@vinesheg/dev-forge 1.0.2

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) 2025 Your Name
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/README.md ADDED
@@ -0,0 +1,323 @@
1
+ [![NPM Version](https://img.shields.io/npm/v/dev-forge)](https://www.npmjs.com/package/dev-forge)
2
+ [![Build Status](https://github.com/vinesheg1/dev-forge/actions/workflows/publish.yml/badge.svg)](https://github.com/vinesheg1/dev-forge/actions)
3
+ [![License](https://img.shields.io/github/license/vinesheg1/dev-forge)](https://github.com/vinesheg1/dev-forge/blob/main/LICENSE)
4
+ # dev-forge
5
+
6
+ > Zero-config developer toolkit with unified CLI for modern projects
7
+
8
+ A single-dependency meta-package that provides comprehensive tooling for linting, formatting, Git hooks, and code quality checks.
9
+
10
+ ## Features
11
+
12
+ ✨ **Zero Configuration** - Works out of the box with sensible defaults
13
+ 🔧 **Single Dependency** - Install once, get everything you need
14
+ ⚡ **High Performance** - Powered by Biome and Lefthook
15
+ 🎯 **Comprehensive Checks** - Linting, formatting, dead code detection, and more
16
+ 🪝 **Git Hooks** - Automatic pre-commit and commit-msg validation
17
+ 📦 **Package Standards** - Validates package.json structure and content
18
+
19
+ ## Tools Included
20
+
21
+ - **[Biome](https://biomejs.dev/)** - Fast linting and formatting for JS/TS/JSON
22
+ - **[Knip](https://knip.dev/)** - Find unused files, dependencies, and exports
23
+ - **[Lefthook](https://github.com/evilmartians/lefthook)** - Fast and powerful Git hooks manager
24
+ - **[Commitlint](https://commitlint.js.org/)** - Enforce conventional commit messages
25
+ - **[Stylelint](https://stylelint.io/)** - Modern CSS/SCSS linter
26
+ - **[npm-package-json-lint](https://npmpackagejsonlint.org/)** - Package.json validator
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install -D @vinesheg/dev-forge```
32
+
33
+ Or with yarn:
34
+
35
+ ```bash
36
+ yarn add --dev @vinesheg/dev-forge
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ 1. **Initialize the toolkit** in your project:
42
+
43
+ ```bash
44
+ npx forge init
45
+ ```
46
+
47
+ This will:
48
+ - Generate configuration files (biome.json, .stylelintrc.json, etc.)
49
+ - Set up Git hooks via Lefthook
50
+ - Add a `prepare` script to your package.json
51
+
52
+ 2. **Run checks** on your codebase:
53
+
54
+ ```bash
55
+ npx forge check
56
+ ```
57
+
58
+ 3. **Auto-fix** issues where possible:
59
+
60
+ ```bash
61
+ npx forge fix
62
+ ```
63
+
64
+ ## CLI Commands
65
+
66
+ ### `forge init`
67
+
68
+ Initialize dev-forge in your project. Creates configuration files and sets up Git hooks.
69
+
70
+ ```bash
71
+ npx forge init [options]
72
+ ```
73
+
74
+ **Options:**
75
+ - `--skip-hooks` - Skip Lefthook installation
76
+
77
+ **What it does:**
78
+ - Creates `biome.json` that extends toolkit defaults
79
+ - Creates `.stylelintrc.json` for CSS/SCSS linting
80
+ - Creates `.npmpackagejsonlintrc.json` for package.json validation
81
+ - Creates `.commitlintrc.json` for commit message validation
82
+ - Copies `lefthook.yml` for Git hook configuration
83
+ - Installs Git hooks via Lefthook
84
+ - Adds `prepare` script to package.json (runs on `npm install`)
85
+
86
+ ### `forge check`
87
+
88
+ Run all quality checks on your codebase in parallel.
89
+
90
+ ```bash
91
+ npx forge check [options]
92
+ ```
93
+
94
+ **Options:**
95
+ - `--no-parallel` - Run checks sequentially instead of in parallel
96
+
97
+ **What it checks:**
98
+ - **Biome**: Lints and checks formatting of JS/TS/JSON files
99
+ - **Stylelint**: Validates CSS/SCSS/Sass files
100
+ - **Knip**: Finds unused files, dependencies, and exports
101
+ - **npm-package-json-lint**: Validates package.json structure
102
+
103
+ ### `forge fix`
104
+
105
+ Auto-fix issues where possible.
106
+
107
+ ```bash
108
+ npx forge fix
109
+ ```
110
+
111
+ **What it fixes:**
112
+ - **Biome**: Applies automatic fixes and formatting
113
+ - **Stylelint**: Fixes auto-fixable CSS/SCSS issues
114
+
115
+ ## Git Hooks
116
+
117
+ After running `forge init`, the following Git hooks are automatically installed:
118
+
119
+ ### Pre-commit Hook
120
+
121
+ Runs before each commit and checks:
122
+ - **Biome** on staged JS/TS/JSON files
123
+ - **Stylelint** on staged CSS/SCSS files
124
+ - **npm-package-json-lint** if package.json is staged
125
+
126
+ ### Commit-msg Hook
127
+
128
+ Runs after entering a commit message and validates:
129
+ - **Commitlint** enforces [Conventional Commits](https://www.conventionalcommits.org/)
130
+
131
+ Example valid commit messages:
132
+ ```
133
+ feat: add user authentication
134
+ fix: resolve memory leak in worker
135
+ docs: update installation instructions
136
+ chore: upgrade dependencies
137
+ ```
138
+
139
+ ## Configuration
140
+
141
+ All tools come pre-configured with sensible defaults, but you can customize them by editing the generated config files:
142
+
143
+ ### Biome (`biome.json`)
144
+
145
+ ```json
146
+ {
147
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
148
+ "extends": ["./node_modules/dev-forge/configs/biome.json"]
149
+ }
150
+ ```
151
+
152
+ Add your own rules or override defaults:
153
+
154
+ ```json
155
+ {
156
+ "extends": ["./node_modules/dev-forge/configs/biome.json"],
157
+ "linter": {
158
+ "rules": {
159
+ "suspicious": {
160
+ "noExplicitAny": "off"
161
+ }
162
+ }
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### Stylelint (`.stylelintrc.json`)
168
+
169
+ ```json
170
+ {
171
+ "extends": ["./node_modules/dev-forge/configs/stylelint.json"]
172
+ }
173
+ ```
174
+
175
+ ### npm-package-json-lint (`.npmpackagejsonlintrc.json`)
176
+
177
+ ```json
178
+ {
179
+ "extends": "./node_modules/dev-forge/configs/pkg-lint.json"
180
+ }
181
+ ```
182
+
183
+ ### Lefthook (`lefthook.yml`)
184
+
185
+ The default configuration runs checks on staged files. You can add custom commands:
186
+
187
+ ```yaml
188
+ pre-commit:
189
+ parallel: true
190
+ commands:
191
+ biome:
192
+ glob: "*.{js,jsx,ts,tsx,json,jsonc}"
193
+ run: npx biome check --staged {staged_files}
194
+
195
+ custom-script:
196
+ run: npm run my-custom-check
197
+ ```
198
+
199
+ ## Package.json Scripts
200
+
201
+ Add these to your `package.json` for convenience:
202
+
203
+ ```json
204
+ {
205
+ "scripts": {
206
+ "prepare": "forge init",
207
+ "lint": "forge check",
208
+ "lint:fix": "forge fix"
209
+ }
210
+ }
211
+ ```
212
+
213
+ Then use:
214
+ ```bash
215
+ npm run lint # Run all checks
216
+ npm run lint:fix # Auto-fix issues
217
+ ```
218
+
219
+ ## CI/CD Integration
220
+
221
+ Add dev-forge checks to your CI pipeline:
222
+
223
+ ### GitHub Actions
224
+
225
+ ```yaml
226
+ name: Quality Checks
227
+
228
+ on: [push, pull_request]
229
+
230
+ jobs:
231
+ lint:
232
+ runs-on: ubuntu-latest
233
+ steps:
234
+ - uses: actions/checkout@v4
235
+ - uses: actions/setup-node@v4
236
+ with:
237
+ node-version: '18'
238
+ - run: npm ci
239
+ - run: npx forge check
240
+ ```
241
+
242
+ ### GitLab CI
243
+
244
+ ```yaml
245
+ lint:
246
+ image: node:18
247
+ script:
248
+ - npm ci
249
+ - npx forge check
250
+ ```
251
+
252
+ ## Knip Configuration
253
+
254
+ Knip automatically detects unused files and dependencies. To customize, create a `knip.json`:
255
+
256
+ ```json
257
+ {
258
+ "entry": ["src/index.ts"],
259
+ "project": ["src/**/*.ts"],
260
+ "ignore": ["**/*.test.ts", "scripts/**"]
261
+ }
262
+ ```
263
+
264
+ ## Troubleshooting
265
+
266
+ ### Git hooks not running
267
+
268
+ If hooks aren't running, try:
269
+
270
+ ```bash
271
+ npx forge init
272
+ ```
273
+
274
+ This will reinstall Lefthook hooks.
275
+
276
+ ### Lefthook installation fails
277
+
278
+ Make sure you have a `.git` directory:
279
+
280
+ ```bash
281
+ git init
282
+ npx forge init
283
+ ```
284
+
285
+ ### Configuration not found
286
+
287
+ Run the init command to generate config files:
288
+
289
+ ```bash
290
+ npx forge init
291
+ ```
292
+
293
+ ## Requirements
294
+
295
+ - Node.js >= 18.0.0
296
+ - Git (for Git hooks)
297
+
298
+ ## Philosophy
299
+
300
+ `dev-forge` follows the principle of **convention over configuration**. It provides:
301
+
302
+ 1. **Sensible defaults** that work for most projects
303
+ 2. **Easy customization** when you need it
304
+ 3. **Minimal setup** - one command to get started
305
+ 4. **Fast feedback** - catch issues before they reach CI/CD
306
+
307
+ ## License
308
+
309
+ MIT
310
+
311
+ ## Contributing
312
+
313
+ Issues and pull requests are welcome! Please follow [Conventional Commits](https://www.conventionalcommits.org/) for commit messages.
314
+
315
+ ## Acknowledgments
316
+
317
+ Built with these excellent tools:
318
+ - [Biome](https://biomejs.dev/)
319
+ - [Knip](https://knip.dev/)
320
+ - [Lefthook](https://github.com/evilmartians/lefthook)
321
+ - [Commitlint](https://commitlint.js.org/)
322
+ - [Stylelint](https://stylelint.io/)
323
+ - [npm-package-json-lint](https://npmpackagejsonlint.org/)
package/bin/cli.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require("commander");
4
+ const path = require("path");
5
+ const { initToolkit } = require("../lib/init");
6
+ const { runCheck, runFix } = require("../lib/runner");
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name("forge")
12
+ .description("Zero-config developer toolkit for modern projects")
13
+ .version(require("../package.json").version);
14
+
15
+ program
16
+ .command("init")
17
+ .description(
18
+ "Initialize dev-forge in your project (generates configs and sets up Git hooks)",
19
+ )
20
+ .option("--skip-hooks", "Skip Lefthook installation")
21
+ .action(async (options) => {
22
+ try {
23
+ await initToolkit(options);
24
+ console.log("✅ dev-forge initialized successfully!");
25
+ } catch (error) {
26
+ console.error("❌ Initialization failed:", error.message);
27
+ process.exit(1);
28
+ }
29
+ });
30
+
31
+ program
32
+ .command("check")
33
+ .description("Run all checks (Biome, Stylelint, Knip, package-json-lint)")
34
+ .option("--no-parallel", "Run checks sequentially instead of in parallel")
35
+ .action(async (options) => {
36
+ try {
37
+ await runCheck(options);
38
+ console.log("✅ All checks passed!");
39
+ } catch (error) {
40
+ console.error("❌ Checks failed:", error.message);
41
+ process.exit(1);
42
+ }
43
+ });
44
+
45
+ program
46
+ .command("fix")
47
+ .description("Auto-fix issues (Biome and Stylelint)")
48
+ .action(async () => {
49
+ try {
50
+ await runFix();
51
+ console.log("✅ Auto-fix completed!");
52
+ } catch (error) {
53
+ console.error("❌ Auto-fix failed:", error.message);
54
+ process.exit(1);
55
+ }
56
+ });
57
+
58
+ program.parse(process.argv);
59
+
60
+ // Show help if no command provided
61
+ if (!process.argv.slice(2).length) {
62
+ program.outputHelp();
63
+ }
@@ -0,0 +1,110 @@
1
+ {
2
+ "$schema": "https://biomejs.dev",
3
+ "organizeImports": {
4
+ "enabled": true
5
+ },
6
+ "linter": {
7
+ "enabled": true,
8
+ "rules": {
9
+ "recommended": true,
10
+ "complexity": {
11
+ "noExtraBooleanCast": "error",
12
+ "noUselessCatch": "error",
13
+ "noUselessConstructor": "error",
14
+ "noUselessLabel": "error",
15
+ "noUselessRename": "error",
16
+ "noUselessSwitchCase": "error",
17
+ "useFlatMap": "warn",
18
+ "useOptionalChain": "warn",
19
+ "useSimplifiedLogicExpression": "warn"
20
+ },
21
+ "correctness": {
22
+ "noConstAssign": "error",
23
+ "noConstantCondition": "error",
24
+ "noEmptyPattern": "error",
25
+ "noGlobalObjectCalls": "error",
26
+ "noInvalidConstructorSuper": "error",
27
+ "noNonoctalDecimalEscape": "error",
28
+ "noPrecisionLoss": "error",
29
+ "noSelfAssign": "error",
30
+ "noSetterReturn": "error",
31
+ "noSwitchDeclarations": "error",
32
+ "noUndeclaredVariables": "error",
33
+ "noUnreachable": "error",
34
+ "noUnreachableSuper": "error",
35
+ "noUnsafeFinally": "error",
36
+ "noUnsafeOptionalChaining": "error",
37
+ "noUnusedLabels": "error",
38
+ "noUnusedVariables": "warn",
39
+ "useIsNan": "error",
40
+ "useValidForDirection": "error"
41
+ },
42
+ "security": {
43
+ "noDangerouslySetInnerHtml": "warn",
44
+ "noDangerouslySetInnerHtmlWithChildren": "error"
45
+ },
46
+ "style": {
47
+ "useConst": "warn",
48
+ "useTemplate": "warn"
49
+ },
50
+ "suspicious": {
51
+ "noArrayIndexKey": "warn",
52
+ "noAssignInExpressions": "warn",
53
+ "noAsyncPromiseExecutor": "error",
54
+ "noCatchAssign": "error",
55
+ "noClassAssign": "error",
56
+ "noCommentText": "warn",
57
+ "noCompareNegZero": "error",
58
+ "noControlCharactersInRegex": "error",
59
+ "noDebugger": "warn",
60
+ "noDoubleEquals": "warn",
61
+ "noDuplicateCase": "error",
62
+ "noDuplicateClassMembers": "error",
63
+ "noDuplicateObjectKeys": "error",
64
+ "noDuplicateParameters": "error",
65
+ "noEmptyBlockStatements": "warn",
66
+ "noExplicitAny": "warn",
67
+ "noExtraNonNullAssertion": "error",
68
+ "noFallthroughSwitchClause": "error",
69
+ "noFunctionAssign": "error",
70
+ "noGlobalAssign": "error",
71
+ "noImportAssign": "error",
72
+ "noMisleadingCharacterClass": "error",
73
+ "noPrototypeBuiltins": "warn",
74
+ "noRedeclare": "error",
75
+ "noShadowRestrictedNames": "error",
76
+ "noUnsafeNegation": "error",
77
+ "useGetterReturn": "error"
78
+ }
79
+ }
80
+ },
81
+ "formatter": {
82
+ "enabled": true,
83
+ "formatWithErrors": false,
84
+ "indentStyle": "space",
85
+ "indentWidth": 2,
86
+ "lineWidth": 100,
87
+ "lineEnding": "lf"
88
+ },
89
+ "javascript": {
90
+ "formatter": {
91
+ "quoteStyle": "single",
92
+ "jsxQuoteStyle": "double",
93
+ "trailingCommas": "es5",
94
+ "semicolons": "always",
95
+ "arrowParentheses": "always",
96
+ "bracketSpacing": true,
97
+ "bracketSameLine": false
98
+ }
99
+ },
100
+ "json": {
101
+ "formatter": {
102
+ "enabled": true,
103
+ "indentWidth": 2
104
+ },
105
+ "parser": {
106
+ "allowComments": true,
107
+ "allowTrailingCommas": false
108
+ }
109
+ }
110
+ }
@@ -0,0 +1,22 @@
1
+ pre-commit:
2
+ parallel: true
3
+ commands:
4
+ biome:
5
+ glob: "*.{js,jsx,ts,tsx,json,jsonc}"
6
+ run: npx biome check --staged {staged_files}
7
+ stage_fixed: true
8
+
9
+ stylelint:
10
+ glob: "*.{css,scss,sass}"
11
+ run: npx stylelint {staged_files}
12
+ stage_fixed: true
13
+
14
+ package-json-lint:
15
+ files: git diff --name-only --cached
16
+ glob: "package.json"
17
+ run: npx npmPkgJsonLint .
18
+
19
+ commit-msg:
20
+ commands:
21
+ commitlint:
22
+ run: npx commitlint --edit {1}
@@ -0,0 +1,54 @@
1
+ {
2
+ "rules": {
3
+ "require-author": "error",
4
+ "require-description": "error",
5
+ "require-keywords": "warning",
6
+ "require-license": "error",
7
+ "require-name": "error",
8
+ "require-repository": "warning",
9
+ "require-version": "error",
10
+ "valid-values-author": ["error", ["string", "object"]],
11
+ "valid-values-license": [
12
+ "error",
13
+ ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC", "GPL-3.0", "UNLICENSED"]
14
+ ],
15
+ "name-format": "error",
16
+ "version-format": "error",
17
+ "description-format": "error",
18
+ "prefer-alphabetical-dependencies": "warning",
19
+ "prefer-alphabetical-devDependencies": "warning",
20
+ "prefer-alphabetical-peerDependencies": "warning",
21
+ "prefer-alphabetical-optionalDependencies": "warning",
22
+ "prefer-no-engineStrict": "error",
23
+ "no-duplicate-properties": "error",
24
+ "prefer-property-order": [
25
+ "warning",
26
+ [
27
+ "name",
28
+ "version",
29
+ "description",
30
+ "keywords",
31
+ "author",
32
+ "license",
33
+ "homepage",
34
+ "repository",
35
+ "bugs",
36
+ "main",
37
+ "module",
38
+ "types",
39
+ "exports",
40
+ "bin",
41
+ "files",
42
+ "scripts",
43
+ "dependencies",
44
+ "devDependencies",
45
+ "peerDependencies",
46
+ "optionalDependencies",
47
+ "engines"
48
+ ]
49
+ ],
50
+ "require-scripts": "warning",
51
+ "no-absolute-version-dependencies": "warning",
52
+ "no-absolute-version-devDependencies": "warning"
53
+ }
54
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "extends": "stylelint-config-standard",
3
+ "rules": {
4
+ "at-rule-no-unknown": [
5
+ true,
6
+ {
7
+ "ignoreAtRules": [
8
+ "tailwind",
9
+ "apply",
10
+ "variants",
11
+ "responsive",
12
+ "screen",
13
+ "layer",
14
+ "mixin",
15
+ "include",
16
+ "extend",
17
+ "function",
18
+ "return",
19
+ "if",
20
+ "else",
21
+ "each",
22
+ "for",
23
+ "while"
24
+ ]
25
+ }
26
+ ],
27
+ "color-hex-length": "short",
28
+ "color-named": "never",
29
+ "declaration-block-no-redundant-longhand-properties": true,
30
+ "declaration-no-important": true,
31
+ "font-family-name-quotes": "always-where-recommended",
32
+ "font-weight-notation": "numeric",
33
+ "function-url-quotes": "always",
34
+ "max-nesting-depth": 4,
35
+ "no-descending-specificity": null,
36
+ "number-max-precision": 4,
37
+ "selector-class-pattern": null,
38
+ "selector-id-pattern": null,
39
+ "selector-max-compound-selectors": 4,
40
+ "selector-max-id": 0,
41
+ "selector-max-specificity": "0,4,0",
42
+ "selector-max-universal": 1,
43
+ "selector-no-qualifying-type": [
44
+ true,
45
+ {
46
+ "ignore": ["attribute", "class"]
47
+ }
48
+ ],
49
+ "shorthand-property-no-redundant-values": true,
50
+ "value-keyword-case": "lower"
51
+ }
52
+ }
package/index.js ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * dev-forge
3
+ * Zero-config developer toolkit with unified CLI
4
+ */
5
+
6
+ const { initToolkit } = require("./lib/init");
7
+ const {
8
+ runCheck,
9
+ runFix,
10
+ runBiomeStaged,
11
+ runStylelintStaged,
12
+ getToolBin,
13
+ runCommand,
14
+ } = require("./lib/runner");
15
+
16
+ module.exports = {
17
+ // Initialization
18
+ initToolkit,
19
+
20
+ // Runners
21
+ runCheck,
22
+ runFix,
23
+ runBiomeStaged,
24
+ runStylelintStaged,
25
+
26
+ // Utilities
27
+ getToolBin,
28
+ runCommand,
29
+ };
package/lib/init.js ADDED
@@ -0,0 +1,170 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+ const execa = require("execa");
4
+
5
+ const CWD = process.cwd();
6
+ const TOOLKIT_ROOT = path.join(__dirname, "..");
7
+
8
+ /**
9
+ * Initialize the toolkit in the user's project
10
+ * This function is idempotent - safe to run multiple times
11
+ */
12
+ async function initToolkit(options = {}) {
13
+ console.log("🔧 Initializing dev-forge...\n");
14
+
15
+ // Step 1: Generate local config files that extend the toolkit's configs
16
+ await generateConfigFiles();
17
+
18
+ // Step 2: Initialize Lefthook (Git hooks)
19
+ if (!options.skipHooks) {
20
+ await initializeLefthook();
21
+ }
22
+
23
+ // Step 3: Update package.json with prepare script
24
+ await updatePackageJson();
25
+
26
+ console.log("\n📦 Configuration files created:");
27
+ console.log(" - biome.json");
28
+ console.log(" - .stylelintrc.json");
29
+ console.log(" - .npmpackagejsonlintrc.json");
30
+ console.log(" - .commitlintrc.json");
31
+ console.log(" - lefthook.yml");
32
+ }
33
+
34
+ /**
35
+ * Generate config files that extend the toolkit's internal configs
36
+ */
37
+ async function generateConfigFiles() {
38
+ const configs = [
39
+ {
40
+ name: "biome.json",
41
+ content: {
42
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
43
+ extends: [
44
+ path
45
+ .relative(CWD, path.join(TOOLKIT_ROOT, "configs/biome.json"))
46
+ .replace(/\\/g, "/"),
47
+ ],
48
+ },
49
+ },
50
+ {
51
+ name: ".stylelintrc.json",
52
+ content: {
53
+ extends: [
54
+ path
55
+ .relative(CWD, path.join(TOOLKIT_ROOT, "configs/stylelint.json"))
56
+ .replace(/\\/g, "/"),
57
+ ],
58
+ },
59
+ },
60
+ {
61
+ name: ".npmpackagejsonlintrc.json",
62
+ content: {
63
+ extends: path
64
+ .relative(CWD, path.join(TOOLKIT_ROOT, "configs/pkg-lint.json"))
65
+ .replace(/\\/g, "/"),
66
+ },
67
+ },
68
+ {
69
+ name: ".commitlintrc.json",
70
+ content: {
71
+ extends: ["@commitlint/config-conventional"],
72
+ },
73
+ },
74
+ ];
75
+
76
+ for (const config of configs) {
77
+ const configPath = path.join(CWD, config.name);
78
+
79
+ // Only create if doesn't exist (idempotent)
80
+ if (!(await fs.pathExists(configPath))) {
81
+ await fs.writeJson(configPath, config.content, { spaces: 2 });
82
+ console.log(` ✓ Created ${config.name}`);
83
+ } else {
84
+ console.log(` ⊙ ${config.name} already exists (skipped)`);
85
+ }
86
+ }
87
+
88
+ // Copy lefthook.yml directly (it doesn't support extends)
89
+ const lefthookSrc = path.join(TOOLKIT_ROOT, "configs/lefthook.yml");
90
+ const lefthookDest = path.join(CWD, "lefthook.yml");
91
+
92
+ if (!(await fs.pathExists(lefthookDest))) {
93
+ await fs.copy(lefthookSrc, lefthookDest);
94
+ console.log(" ✓ Created lefthook.yml");
95
+ } else {
96
+ console.log(" ⊙ lefthook.yml already exists (skipped)");
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Initialize Lefthook Git hooks
102
+ */
103
+ async function initializeLefthook() {
104
+ console.log("\n🪝 Installing Git hooks...");
105
+
106
+ try {
107
+ const lefthookBin = path.join(TOOLKIT_ROOT, "node_modules/.bin/lefthook");
108
+
109
+ // Check if .git directory exists
110
+ const gitDir = path.join(CWD, ".git");
111
+ if (!(await fs.pathExists(gitDir))) {
112
+ console.log(
113
+ " ⚠ No .git directory found. Skipping Git hooks installation.",
114
+ );
115
+ console.log(' Run "git init" first, then run "forge init" again.');
116
+ return;
117
+ }
118
+
119
+ await execa(lefthookBin, ["install"], {
120
+ cwd: CWD,
121
+ stdio: "inherit",
122
+ });
123
+
124
+ console.log(" ✓ Lefthook installed successfully");
125
+ } catch (error) {
126
+ console.error(" ⚠ Lefthook installation failed:", error.message);
127
+ console.error(' You may need to run "lefthook install" manually.');
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Update user's package.json to include the prepare script
133
+ */
134
+ async function updatePackageJson() {
135
+ console.log("\n📝 Updating package.json...");
136
+
137
+ const pkgPath = path.join(CWD, "package.json");
138
+
139
+ if (!(await fs.pathExists(pkgPath))) {
140
+ console.log(" ⚠ No package.json found in current directory");
141
+ return;
142
+ }
143
+
144
+ const pkg = await fs.readJson(pkgPath);
145
+
146
+ // Initialize scripts if it doesn't exist
147
+ if (!pkg.scripts) {
148
+ pkg.scripts = {};
149
+ }
150
+
151
+ // Add prepare script if it doesn't exist or doesn't include forge init
152
+ const prepareScript = "forge init";
153
+
154
+ if (!pkg.scripts.prepare) {
155
+ pkg.scripts.prepare = prepareScript;
156
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
157
+ console.log(' ✓ Added "prepare" script to package.json');
158
+ } else if (!pkg.scripts.prepare.includes("forge init")) {
159
+ // Append to existing prepare script
160
+ pkg.scripts.prepare = `${pkg.scripts.prepare} && ${prepareScript}`;
161
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
162
+ console.log(' ✓ Updated "prepare" script in package.json');
163
+ } else {
164
+ console.log(' ⊙ "prepare" script already includes forge init (skipped)');
165
+ }
166
+ }
167
+
168
+ module.exports = {
169
+ initToolkit,
170
+ };
package/lib/runner.js ADDED
@@ -0,0 +1,187 @@
1
+ const execa = require("execa");
2
+ const path = require("path");
3
+ const fs = require("fs-extra");
4
+
5
+ const CWD = process.cwd();
6
+ const TOOLKIT_ROOT = path.join(__dirname, "..");
7
+
8
+ /**
9
+ * Get the path to a tool's binary in the toolkit's node_modules
10
+ */
11
+ function getToolBin(toolName) {
12
+ return path.join(TOOLKIT_ROOT, "node_modules/.bin", toolName);
13
+ }
14
+
15
+ /**
16
+ * Run a command and handle output
17
+ */
18
+ async function runCommand(command, args, options = {}) {
19
+ const toolName = path.basename(command);
20
+ console.log(`\n🔍 Running ${toolName}...`);
21
+
22
+ try {
23
+ const result = await execa(command, args, {
24
+ cwd: CWD,
25
+ stdio: "inherit",
26
+ ...options,
27
+ });
28
+
29
+ console.log(`✅ ${toolName} completed successfully`);
30
+ return result;
31
+ } catch (error) {
32
+ console.error(`❌ ${toolName} failed`);
33
+ throw error;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Run all checks in parallel or sequentially
39
+ */
40
+ async function runCheck(options = {}) {
41
+ console.log("🔎 Running comprehensive checks...\n");
42
+
43
+ const checks = [
44
+ async () => {
45
+ // Biome check
46
+ const biomeBin = getToolBin("biome");
47
+ return runCommand(biomeBin, ["check", "."]);
48
+ },
49
+ async () => {
50
+ // Stylelint check
51
+ const stylelintBin = getToolBin("stylelint");
52
+ const patterns = ["**/*.css", "**/*.scss", "**/*.sass"];
53
+
54
+ // Check if any style files exist
55
+ let hasStyleFiles = false;
56
+ for (const pattern of patterns) {
57
+ const files = await fs.pathExists(
58
+ path.join(CWD, pattern.split("/")[0]),
59
+ );
60
+ if (files) {
61
+ hasStyleFiles = true;
62
+ break;
63
+ }
64
+ }
65
+
66
+ if (!hasStyleFiles) {
67
+ console.log("\n⊙ Stylelint skipped (no style files found)");
68
+ return;
69
+ }
70
+
71
+ return runCommand(stylelintBin, patterns);
72
+ },
73
+ async () => {
74
+ // Knip check
75
+ const knipBin = getToolBin("knip");
76
+ return runCommand(knipBin, []);
77
+ },
78
+ async () => {
79
+ // npm-package-json-lint
80
+ const pkgLintBin = getToolBin("npmPkgJsonLint");
81
+ const pkgPath = path.join(CWD, "package.json");
82
+
83
+ if (!(await fs.pathExists(pkgPath))) {
84
+ console.log("\n⊙ package-json-lint skipped (no package.json found)");
85
+ return;
86
+ }
87
+
88
+ return runCommand(pkgLintBin, ["."]);
89
+ },
90
+ ];
91
+
92
+ if (options.parallel !== false) {
93
+ // Run in parallel
94
+ const results = await Promise.allSettled(checks.map((check) => check()));
95
+
96
+ const failures = results.filter((r) => r.status === "rejected");
97
+ if (failures.length > 0) {
98
+ throw new Error(`${failures.length} check(s) failed`);
99
+ }
100
+ } else {
101
+ // Run sequentially
102
+ for (const check of checks) {
103
+ await check();
104
+ }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Run auto-fix operations
110
+ */
111
+ async function runFix() {
112
+ console.log("🔧 Running auto-fix operations...\n");
113
+
114
+ // Biome fix
115
+ const biomeBin = getToolBin("biome");
116
+ await runCommand(biomeBin, ["check", "--write", "."]);
117
+
118
+ // Stylelint fix
119
+ const stylelintBin = getToolBin("stylelint");
120
+ const patterns = ["**/*.css", "**/*.scss", "**/*.sass"];
121
+
122
+ try {
123
+ await runCommand(stylelintBin, [...patterns, "--fix"], { reject: false });
124
+ } catch (error) {
125
+ // Stylelint might fail if no files found, which is okay
126
+ console.log(
127
+ " ⊙ Stylelint fix completed (some files may not have been fixable)",
128
+ );
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Run Biome on staged files (for Git hooks)
134
+ */
135
+ async function runBiomeStaged(files) {
136
+ if (!files || files.length === 0) {
137
+ console.log("⊙ No staged files to check");
138
+ return;
139
+ }
140
+
141
+ const biomeBin = getToolBin("biome");
142
+ const jsFiles = files.filter((f) => /\.(js|jsx|ts|tsx|json|jsonc)$/.test(f));
143
+
144
+ if (jsFiles.length === 0) {
145
+ return;
146
+ }
147
+
148
+ console.log(`🔍 Checking ${jsFiles.length} file(s) with Biome...`);
149
+ await execa(biomeBin, ["check", ...jsFiles], {
150
+ cwd: CWD,
151
+ stdio: "inherit",
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Run Stylelint on staged files (for Git hooks)
157
+ */
158
+ async function runStylelintStaged(files) {
159
+ if (!files || files.length === 0) {
160
+ console.log("⊙ No staged style files to check");
161
+ return;
162
+ }
163
+
164
+ const stylelintBin = getToolBin("stylelint");
165
+ const styleFiles = files.filter((f) => /\.(css|scss|sass)$/.test(f));
166
+
167
+ if (styleFiles.length === 0) {
168
+ return;
169
+ }
170
+
171
+ console.log(
172
+ `🎨 Checking ${styleFiles.length} style file(s) with Stylelint...`,
173
+ );
174
+ await execa(stylelintBin, styleFiles, {
175
+ cwd: CWD,
176
+ stdio: "inherit",
177
+ });
178
+ }
179
+
180
+ module.exports = {
181
+ runCheck,
182
+ runFix,
183
+ runBiomeStaged,
184
+ runStylelintStaged,
185
+ getToolBin,
186
+ runCommand,
187
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@vinesheg/dev-forge",
3
+ "version": "1.0.2",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Zero-config developer toolkit with unified CLI for linting, formatting, and Git hooks",
8
+ "keywords": [
9
+ "developer-tools",
10
+ "linting",
11
+ "formatting",
12
+ "git-hooks",
13
+ "biome",
14
+ "lefthook",
15
+ "knip",
16
+ "commitlint",
17
+ "stylelint"
18
+ ],
19
+ "author": "Vinesh EG <vinesheg@gmail.com>",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/vinesheg1/dev-forge.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/vinesheg1/dev-forge/issues"
27
+ },
28
+ "homepage": "https://github.com/vinesheg1/dev-forge#readme",
29
+ "main": "index.js",
30
+ "types": "index.d.ts",
31
+ "bin": {
32
+ "forge": "bin/cli.js"
33
+ },
34
+ "files": [
35
+ "bin",
36
+ "lib",
37
+ "configs",
38
+ "README.md"
39
+ ],
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "dependencies": {
44
+ "@biomejs/biome": "^2.3.14",
45
+ "@commitlint/cli": "^19.5.0",
46
+ "@commitlint/config-conventional": "^19.5.0",
47
+ "commander": "^12.1.0",
48
+ "execa": "^5.1.1",
49
+ "fs-extra": "^11.2.0",
50
+ "knip": "^5.38.2",
51
+ "lefthook": "^1.9.3",
52
+ "npm-package-json-lint": "^8.0.0",
53
+ "stylelint": "^16.10.0",
54
+ "stylelint-config-standard": "^36.0.1"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^20.17.6"
58
+ },
59
+ "scripts": {
60
+ "forge": "node ./bin/cli.js",
61
+ "test": "echo \"Error: no test specified\" && exit 1"
62
+ }
63
+ }