start-command 0.3.1

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.
Files changed (38) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.changeset/isolation-support.md +30 -0
  4. package/.github/workflows/release.yml +292 -0
  5. package/.husky/pre-commit +1 -0
  6. package/.prettierignore +6 -0
  7. package/.prettierrc +10 -0
  8. package/CHANGELOG.md +24 -0
  9. package/LICENSE +24 -0
  10. package/README.md +249 -0
  11. package/REQUIREMENTS.md +229 -0
  12. package/bun.lock +453 -0
  13. package/bunfig.toml +3 -0
  14. package/eslint.config.mjs +122 -0
  15. package/experiments/debug-regex.js +49 -0
  16. package/experiments/isolation-design.md +142 -0
  17. package/experiments/test-cli.sh +42 -0
  18. package/experiments/test-substitution.js +143 -0
  19. package/package.json +63 -0
  20. package/scripts/changeset-version.mjs +38 -0
  21. package/scripts/check-file-size.mjs +103 -0
  22. package/scripts/create-github-release.mjs +93 -0
  23. package/scripts/create-manual-changeset.mjs +89 -0
  24. package/scripts/format-github-release.mjs +83 -0
  25. package/scripts/format-release-notes.mjs +219 -0
  26. package/scripts/instant-version-bump.mjs +121 -0
  27. package/scripts/publish-to-npm.mjs +129 -0
  28. package/scripts/setup-npm.mjs +37 -0
  29. package/scripts/validate-changeset.mjs +107 -0
  30. package/scripts/version-and-commit.mjs +237 -0
  31. package/src/bin/cli.js +670 -0
  32. package/src/lib/args-parser.js +259 -0
  33. package/src/lib/isolation.js +419 -0
  34. package/src/lib/substitution.js +323 -0
  35. package/src/lib/substitutions.lino +308 -0
  36. package/test/args-parser.test.js +389 -0
  37. package/test/isolation.test.js +248 -0
  38. package/test/substitution.test.js +236 -0
@@ -0,0 +1,122 @@
1
+ import js from '@eslint/js';
2
+ import prettierConfig from 'eslint-config-prettier';
3
+ import prettierPlugin from 'eslint-plugin-prettier';
4
+
5
+ export default [
6
+ js.configs.recommended,
7
+ prettierConfig,
8
+ {
9
+ files: ['**/*.js'],
10
+ plugins: {
11
+ prettier: prettierPlugin,
12
+ },
13
+ languageOptions: {
14
+ ecmaVersion: 'latest',
15
+ sourceType: 'commonjs',
16
+ globals: {
17
+ // CommonJS globals
18
+ require: 'readonly',
19
+ module: 'readonly',
20
+ exports: 'readonly',
21
+ __dirname: 'readonly',
22
+ __filename: 'readonly',
23
+ // Node.js/Bun globals
24
+ console: 'readonly',
25
+ process: 'readonly',
26
+ Buffer: 'readonly',
27
+ fetch: 'readonly',
28
+ Bun: 'readonly',
29
+ },
30
+ },
31
+ rules: {
32
+ // Prettier integration
33
+ 'prettier/prettier': 'error',
34
+
35
+ // Code quality rules
36
+ 'no-unused-vars': 'error',
37
+ 'no-console': 'off', // Allow console in this project
38
+ 'no-debugger': 'error',
39
+
40
+ // Best practices
41
+ eqeqeq: ['error', 'always'],
42
+ curly: ['error', 'all'],
43
+ 'no-var': 'error',
44
+ 'prefer-const': 'error',
45
+ 'prefer-arrow-callback': 'error',
46
+ 'no-duplicate-imports': 'error',
47
+
48
+ // ES6+ features
49
+ 'arrow-body-style': ['error', 'as-needed'],
50
+ 'object-shorthand': ['error', 'always'],
51
+ 'prefer-template': 'error',
52
+
53
+ // Async/await
54
+ 'no-async-promise-executor': 'error',
55
+ 'require-await': 'warn',
56
+
57
+ // Comments and documentation
58
+ 'spaced-comment': ['error', 'always', { markers: ['/'] }],
59
+ },
60
+ },
61
+ {
62
+ // ES module files (.mjs)
63
+ files: ['**/*.mjs'],
64
+ plugins: {
65
+ prettier: prettierPlugin,
66
+ },
67
+ languageOptions: {
68
+ ecmaVersion: 'latest',
69
+ sourceType: 'module',
70
+ globals: {
71
+ // Node.js/Bun globals
72
+ console: 'readonly',
73
+ process: 'readonly',
74
+ Buffer: 'readonly',
75
+ __dirname: 'readonly',
76
+ __filename: 'readonly',
77
+ fetch: 'readonly',
78
+ Bun: 'readonly',
79
+ },
80
+ },
81
+ rules: {
82
+ 'prettier/prettier': 'error',
83
+ 'no-unused-vars': 'error',
84
+ 'no-console': 'off',
85
+ 'no-debugger': 'error',
86
+ eqeqeq: ['error', 'always'],
87
+ curly: ['error', 'all'],
88
+ 'no-var': 'error',
89
+ 'prefer-const': 'error',
90
+ 'prefer-arrow-callback': 'error',
91
+ 'no-duplicate-imports': 'error',
92
+ 'arrow-body-style': ['error', 'as-needed'],
93
+ 'object-shorthand': ['error', 'always'],
94
+ 'prefer-template': 'error',
95
+ 'no-async-promise-executor': 'error',
96
+ 'require-await': 'warn',
97
+ 'spaced-comment': ['error', 'always', { markers: ['/'] }],
98
+ },
99
+ },
100
+ {
101
+ // Test files and experiments have different requirements
102
+ files: [
103
+ 'test/**/*.js',
104
+ 'tests/**/*.js',
105
+ '**/*.test.js',
106
+ 'experiments/**/*.js',
107
+ ],
108
+ rules: {
109
+ 'require-await': 'off', // Async functions without await are common in tests
110
+ 'no-unused-vars': 'warn', // Relax for experiments
111
+ },
112
+ },
113
+ {
114
+ ignores: [
115
+ 'node_modules/**',
116
+ 'coverage/**',
117
+ 'dist/**',
118
+ '*.min.js',
119
+ '.eslintcache',
120
+ ],
121
+ },
122
+ ];
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Debug script to understand regex generation
4
+ */
5
+
6
+ const { createRule, parseLinoContent } = require('../src/lib/substitution');
7
+
8
+ // Test creating a simple rule
9
+ const rule = createRule(
10
+ 'install $packageName npm package',
11
+ 'npm install $packageName'
12
+ );
13
+
14
+ console.log('Pattern:', 'install $packageName npm package');
15
+ console.log('Replacement:', 'npm install $packageName');
16
+ console.log('Generated Regex:', rule.regex);
17
+ console.log('Variables:', rule.variables);
18
+ console.log('');
19
+
20
+ // Test matching
21
+ const input = 'install gh-upload-log npm package';
22
+ const match = input.match(rule.regex);
23
+ console.log('Input:', input);
24
+ console.log('Match result:', match);
25
+
26
+ if (match) {
27
+ console.log('Groups:', match.groups);
28
+ }
29
+
30
+ // Test simpler lino content
31
+ const simpleContent = `
32
+ (install $packageName npm package)
33
+ (npm install $packageName)
34
+ `;
35
+
36
+ const rules = parseLinoContent(simpleContent);
37
+ console.log(
38
+ '\nParsed rules:',
39
+ JSON.stringify(
40
+ rules,
41
+ (key, value) => {
42
+ if (key === 'regex') {
43
+ return value.toString();
44
+ }
45
+ return value;
46
+ },
47
+ 2
48
+ )
49
+ );
@@ -0,0 +1,142 @@
1
+ # Isolation Support Design
2
+
3
+ ## Overview
4
+
5
+ This document outlines the design for adding process isolation support to start-command.
6
+
7
+ ## Supported Isolation Backends
8
+
9
+ ### Terminal Multiplexers
10
+
11
+ 1. **screen** - GNU Screen, classic session manager
12
+ 2. **tmux** - Modern terminal multiplexer
13
+ 3. **zellij** - Modern, user-friendly multiplexer
14
+
15
+ ### Container Isolation
16
+
17
+ 4. **docker** - Docker containers
18
+
19
+ ## Command Syntax
20
+
21
+ Two syntax patterns are supported:
22
+
23
+ ```bash
24
+ # Pattern 1: Using -- separator
25
+ $ [wrapper-options] -- [command] [command-options]
26
+
27
+ # Pattern 2: Options before command
28
+ $ [wrapper-options] command [command-options]
29
+ ```
30
+
31
+ ### Wrapper Options
32
+
33
+ - `--isolated <backend>` or `-i <backend>`: Run command in isolated environment
34
+ - Backends: `screen`, `tmux`, `docker`, `zellij`
35
+ - `--attached` or `-a`: Run in attached mode (foreground)
36
+ - `--detached` or `-d`: Run in detached mode (background)
37
+ - `--session <name>` or `-s <name>`: Name for the session (optional)
38
+
39
+ ### Examples
40
+
41
+ ```bash
42
+ # Run in tmux (attached by default for multiplexers)
43
+ $ --isolated tmux -- npm test
44
+
45
+ # Run in screen detached
46
+ $ --isolated screen --detached -- npm start
47
+
48
+ # Run in docker container
49
+ $ --isolated docker --image node:20 -- npm install
50
+
51
+ # Short form
52
+ $ -i tmux -d npm start
53
+ ```
54
+
55
+ ## Mode Behavior
56
+
57
+ ### Attached Mode (--attached)
58
+
59
+ - Default for terminal multiplexers (screen, tmux, zellij)
60
+ - Command runs in foreground
61
+ - User can interact with the terminal
62
+ - For docker: runs with -it flags
63
+
64
+ ### Detached Mode (--detached)
65
+
66
+ - Command runs in background
67
+ - For multiplexers: creates a session that can be reattached later
68
+ - For docker: runs with -d flag
69
+ - Session info is printed for later access
70
+
71
+ ### Error Handling
72
+
73
+ - If both --attached and --detached are specified, throw an error
74
+ - Error message should ask user to choose only one
75
+
76
+ ## Implementation Details
77
+
78
+ ### Session Naming
79
+
80
+ - Auto-generate session name: `start-{timestamp}-{random}`
81
+ - Allow custom name via --session option
82
+ - Session names used for reattachment
83
+
84
+ ### Backend Commands
85
+
86
+ #### Screen
87
+
88
+ ```bash
89
+ # Attached
90
+ screen -S <session> bash -c '<command>'
91
+
92
+ # Detached
93
+ screen -dmS <session> bash -c '<command>'
94
+ ```
95
+
96
+ #### Tmux
97
+
98
+ ```bash
99
+ # Attached
100
+ tmux new-session -s <session> '<command>'
101
+
102
+ # Detached
103
+ tmux new-session -d -s <session> '<command>'
104
+ ```
105
+
106
+ #### Zellij
107
+
108
+ ```bash
109
+ # Attached
110
+ zellij run -- <command>
111
+
112
+ # Detached (via layout file or action)
113
+ zellij -s <session> action new-pane -- <command>
114
+ ```
115
+
116
+ #### Docker
117
+
118
+ ```bash
119
+ # Attached
120
+ docker run -it --name <name> <image> <command>
121
+
122
+ # Detached
123
+ docker run -d --name <name> <image> <command>
124
+ ```
125
+
126
+ ## Testing Strategy
127
+
128
+ ### Unit Tests
129
+
130
+ - Argument parsing tests
131
+ - Backend detection tests
132
+ - Session name generation tests
133
+ - Conflict detection (attached + detached)
134
+
135
+ ### Integration Tests
136
+
137
+ - Actual execution with mocked backends
138
+ - End-to-end tests with real backends (when available)
139
+
140
+ ### E2E Tests
141
+
142
+ - Full workflow tests with screen/tmux (if installed)
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ # Test script for start-command CLI
3
+
4
+ set -e
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ CLI_PATH="${SCRIPT_DIR}/../bin/cli.js"
8
+
9
+ echo "=== Testing start-command CLI ==="
10
+ echo ""
11
+
12
+ # Test 1: No arguments - should show usage
13
+ echo "Test 1: No arguments (should show usage)"
14
+ node "$CLI_PATH" || true
15
+ echo ""
16
+
17
+ # Test 2: Successful command
18
+ echo "Test 2: Successful command (echo)"
19
+ node "$CLI_PATH" echo "Hello from test!"
20
+ echo ""
21
+
22
+ # Test 3: List directory
23
+ echo "Test 3: List directory (ls)"
24
+ node "$CLI_PATH" ls -la "$SCRIPT_DIR"
25
+ echo ""
26
+
27
+ # Test 4: Failing command (system command)
28
+ echo "Test 4: Failing system command (false) - should NOT detect repository"
29
+ node "$CLI_PATH" false || true
30
+ echo ""
31
+
32
+ # Test 5: Non-existent command
33
+ echo "Test 5: Non-existent command - should NOT detect repository"
34
+ node "$CLI_PATH" this_command_does_not_exist_xyz123 || true
35
+ echo ""
36
+
37
+ # Test 6: Check log file creation
38
+ echo "Test 6: Verify log files are created"
39
+ ls -la /tmp/start-command-*.log 2>/dev/null | head -5 || echo "No log files found (unexpected)"
40
+ echo ""
41
+
42
+ echo "=== All tests completed ==="
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Test script for the substitution engine
4
+ * Tests pattern matching with various inputs
5
+ */
6
+
7
+ const path = require('path');
8
+
9
+ // Import the substitution module
10
+ const {
11
+ parseLinoContent,
12
+ matchAndSubstitute,
13
+ loadDefaultSubstitutions,
14
+ processCommand,
15
+ } = require('../src/lib/substitution');
16
+
17
+ const testCases = [
18
+ // NPM install patterns
19
+ {
20
+ input: 'install gh-upload-log npm package',
21
+ expected: 'npm install gh-upload-log',
22
+ description: 'Basic npm install',
23
+ },
24
+ {
25
+ input: 'install 0.0.1 version of gh-upload-log npm package',
26
+ expected: 'npm install gh-upload-log@0.0.1',
27
+ description: 'npm install with version',
28
+ },
29
+ {
30
+ input: 'install lodash npm package globally',
31
+ expected: 'npm install -g lodash',
32
+ description: 'Global npm install',
33
+ },
34
+ {
35
+ input: 'install 4.17.21 version of lodash npm package globally',
36
+ expected: 'npm install -g lodash@4.17.21',
37
+ description: 'Global npm install with version',
38
+ },
39
+ {
40
+ input: 'uninstall lodash npm package',
41
+ expected: 'npm uninstall lodash',
42
+ description: 'npm uninstall',
43
+ },
44
+
45
+ // Git patterns
46
+ {
47
+ input: 'clone https://github.com/user/repo repository',
48
+ expected: 'git clone https://github.com/user/repo',
49
+ description: 'Git clone with URL',
50
+ },
51
+ {
52
+ input: 'clone git@github.com:user/repo.git repository',
53
+ expected: 'git clone git@github.com:user/repo.git',
54
+ description: 'Git clone with SSH URL',
55
+ },
56
+ {
57
+ input: 'checkout main branch',
58
+ expected: 'git checkout main',
59
+ description: 'Git checkout branch',
60
+ },
61
+ {
62
+ input: 'create feature-x branch',
63
+ expected: 'git checkout -b feature-x',
64
+ description: 'Git create branch',
65
+ },
66
+
67
+ // Common operations
68
+ {
69
+ input: 'list files',
70
+ expected: 'ls -la',
71
+ description: 'List files',
72
+ },
73
+ {
74
+ input: 'show current directory',
75
+ expected: 'pwd',
76
+ description: 'Show working directory',
77
+ },
78
+ {
79
+ input: 'create my-project directory',
80
+ expected: 'mkdir -p my-project',
81
+ description: 'Create directory',
82
+ },
83
+
84
+ // Python patterns
85
+ {
86
+ input: 'install requests python package',
87
+ expected: 'pip install requests',
88
+ description: 'pip install',
89
+ },
90
+
91
+ // Non-matching patterns (should return original)
92
+ {
93
+ input: 'echo hello world',
94
+ expected: 'echo hello world',
95
+ description: 'Non-matching command (pass through)',
96
+ },
97
+ {
98
+ input: 'npm test',
99
+ expected: 'npm test',
100
+ description: 'Regular npm command (pass through)',
101
+ },
102
+ ];
103
+
104
+ console.log('=== Substitution Engine Tests ===\n');
105
+
106
+ // Load default substitutions
107
+ const rules = loadDefaultSubstitutions();
108
+ console.log(`Loaded ${rules.length} substitution rules\n`);
109
+
110
+ let passed = 0;
111
+ let failed = 0;
112
+
113
+ for (const test of testCases) {
114
+ const result = matchAndSubstitute(test.input, rules);
115
+
116
+ if (result.command === test.expected) {
117
+ console.log(`āœ“ PASS: ${test.description}`);
118
+ console.log(` Input: "${test.input}"`);
119
+ console.log(` Output: "${result.command}"`);
120
+ console.log(
121
+ ` Matched: ${result.matched ? result.rule.pattern : 'none (pass through)'}`
122
+ );
123
+ passed++;
124
+ } else {
125
+ console.log(`āœ— FAIL: ${test.description}`);
126
+ console.log(` Input: "${test.input}"`);
127
+ console.log(` Expected: "${test.expected}"`);
128
+ console.log(` Got: "${result.command}"`);
129
+ console.log(` Matched: ${result.matched ? result.rule.pattern : 'none'}`);
130
+ failed++;
131
+ }
132
+ console.log('');
133
+ }
134
+
135
+ console.log('=== Summary ===');
136
+ console.log(`Passed: ${passed}/${testCases.length}`);
137
+ console.log(`Failed: ${failed}/${testCases.length}`);
138
+
139
+ if (failed > 0) {
140
+ process.exit(1);
141
+ }
142
+
143
+ console.log('\n=== All tests passed! ===');
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "start-command",
3
+ "version": "0.3.1",
4
+ "description": "Gamification of coding, execute any command with ability to auto-report issues on GitHub",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "$": "./src/bin/cli.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node --test test/",
11
+ "lint": "eslint .",
12
+ "lint:fix": "eslint . --fix",
13
+ "format": "prettier --write .",
14
+ "format:check": "prettier --check .",
15
+ "check:file-size": "node scripts/check-file-size.mjs",
16
+ "check": "npm run lint && npm run format:check && npm run check:file-size",
17
+ "prepare": "husky || true",
18
+ "changeset": "changeset",
19
+ "changeset:version": "node scripts/changeset-version.mjs",
20
+ "changeset:publish": "changeset publish",
21
+ "changeset:status": "changeset status --since=origin/main"
22
+ },
23
+ "keywords": [
24
+ "cli",
25
+ "command",
26
+ "gamification",
27
+ "github",
28
+ "automation"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/link-foundation/start.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/link-foundation/start/issues"
41
+ },
42
+ "homepage": "https://github.com/link-foundation/start#readme",
43
+ "devDependencies": {
44
+ "@changesets/cli": "^2.29.7",
45
+ "eslint": "^9.38.0",
46
+ "eslint-config-prettier": "^10.1.8",
47
+ "eslint-plugin-prettier": "^5.5.4",
48
+ "husky": "^9.1.7",
49
+ "lint-staged": "^16.2.6",
50
+ "prettier": "^3.6.2"
51
+ },
52
+ "lint-staged": {
53
+ "*.{js,mjs,cjs}": [
54
+ "eslint --fix --max-warnings 6",
55
+ "prettier --write",
56
+ "prettier --check"
57
+ ],
58
+ "*.md": [
59
+ "prettier --write",
60
+ "prettier --check"
61
+ ]
62
+ }
63
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Custom changeset version script that ensures package-lock.json is synchronized
5
+ * with package.json after version bumps.
6
+ *
7
+ * This script:
8
+ * 1. Runs `changeset version` to update package versions
9
+ * 2. Runs `npm install` to synchronize package-lock.json with the new versions
10
+ *
11
+ * Uses link-foundation libraries:
12
+ * - use-m: Dynamic package loading without package.json dependencies
13
+ * - command-stream: Modern shell command execution with streaming support
14
+ */
15
+
16
+ // Load use-m dynamically
17
+ const { use } = eval(
18
+ await (await fetch('https://unpkg.com/use-m/use.js')).text()
19
+ );
20
+
21
+ // Import command-stream for shell command execution
22
+ const { $ } = await use('command-stream');
23
+
24
+ try {
25
+ console.log('Running changeset version...');
26
+ await $`npx changeset version`;
27
+
28
+ console.log('\nSynchronizing package-lock.json...');
29
+ await $`npm install --package-lock-only`;
30
+
31
+ console.log('\nāœ… Version bump complete with synchronized package-lock.json');
32
+ } catch (error) {
33
+ console.error('Error during version bump:', error.message);
34
+ if (process.env.DEBUG) {
35
+ console.error('Stack trace:', error.stack);
36
+ }
37
+ process.exit(1);
38
+ }
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Check for files exceeding the maximum allowed line count
5
+ * Exits with error code 1 if any files exceed the limit
6
+ */
7
+
8
+ import { readdir, readFile } from 'fs/promises';
9
+ import { join, relative } from 'path';
10
+
11
+ const MAX_LINES = 1000;
12
+ const FILE_EXTENSIONS = ['.js', '.mjs', '.cjs'];
13
+
14
+ /**
15
+ * Recursively find all JavaScript files in a directory
16
+ * @param {string} dir - Directory to search
17
+ * @param {string[]} filesToExclude - Patterns to exclude
18
+ * @returns {Promise<string[]>} Array of file paths
19
+ */
20
+ async function findJavaScriptFiles(dir, filesToExclude = []) {
21
+ const files = [];
22
+ const entries = await readdir(dir, { withFileTypes: true });
23
+
24
+ for (const entry of entries) {
25
+ const fullPath = join(dir, entry.name);
26
+ const relativePath = relative(process.cwd(), fullPath);
27
+
28
+ // Skip excluded directories and files
29
+ if (
30
+ filesToExclude.some((pattern) =>
31
+ relativePath.includes(pattern.replace(/\*\*/g, ''))
32
+ )
33
+ ) {
34
+ continue;
35
+ }
36
+
37
+ if (entry.isDirectory()) {
38
+ files.push(...(await findJavaScriptFiles(fullPath, filesToExclude)));
39
+ } else if (FILE_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
40
+ files.push(fullPath);
41
+ }
42
+ }
43
+
44
+ return files;
45
+ }
46
+
47
+ /**
48
+ * Count lines in a file
49
+ * @param {string} filePath - Path to the file
50
+ * @returns {Promise<number>} Number of lines
51
+ */
52
+ async function countLines(filePath) {
53
+ const content = await readFile(filePath, 'utf-8');
54
+ return content.split('\n').length;
55
+ }
56
+
57
+ /**
58
+ * Main function
59
+ */
60
+ async function main() {
61
+ const excludePatterns = ['node_modules', 'coverage', 'dist', '.git', 'build'];
62
+
63
+ console.log(
64
+ `\nChecking JavaScript files for maximum ${MAX_LINES} lines...\n`
65
+ );
66
+
67
+ const files = await findJavaScriptFiles(process.cwd(), excludePatterns);
68
+ const violations = [];
69
+
70
+ for (const file of files) {
71
+ const lineCount = await countLines(file);
72
+ if (lineCount > MAX_LINES) {
73
+ violations.push({
74
+ file: relative(process.cwd(), file),
75
+ lines: lineCount,
76
+ });
77
+ }
78
+ }
79
+
80
+ if (violations.length === 0) {
81
+ console.log('āœ“ All files are within the line limit\n');
82
+ process.exit(0);
83
+ } else {
84
+ console.error('āœ— Found files exceeding the line limit:\n');
85
+ for (const violation of violations) {
86
+ console.error(
87
+ ` ${violation.file}: ${violation.lines} lines (exceeds ${MAX_LINES})`
88
+ );
89
+ }
90
+ console.error(
91
+ `\nPlease refactor these files to be under ${MAX_LINES} lines\n`
92
+ );
93
+ process.exit(1);
94
+ }
95
+ }
96
+
97
+ main().catch((error) => {
98
+ console.error('Error:', error.message);
99
+ if (process.env.DEBUG) {
100
+ console.error('Stack trace:', error.stack);
101
+ }
102
+ process.exit(1);
103
+ });