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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.changeset/isolation-support.md +30 -0
- package/.github/workflows/release.yml +292 -0
- package/.husky/pre-commit +1 -0
- package/.prettierignore +6 -0
- package/.prettierrc +10 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +24 -0
- package/README.md +249 -0
- package/REQUIREMENTS.md +229 -0
- package/bun.lock +453 -0
- package/bunfig.toml +3 -0
- package/eslint.config.mjs +122 -0
- package/experiments/debug-regex.js +49 -0
- package/experiments/isolation-design.md +142 -0
- package/experiments/test-cli.sh +42 -0
- package/experiments/test-substitution.js +143 -0
- package/package.json +63 -0
- package/scripts/changeset-version.mjs +38 -0
- package/scripts/check-file-size.mjs +103 -0
- package/scripts/create-github-release.mjs +93 -0
- package/scripts/create-manual-changeset.mjs +89 -0
- package/scripts/format-github-release.mjs +83 -0
- package/scripts/format-release-notes.mjs +219 -0
- package/scripts/instant-version-bump.mjs +121 -0
- package/scripts/publish-to-npm.mjs +129 -0
- package/scripts/setup-npm.mjs +37 -0
- package/scripts/validate-changeset.mjs +107 -0
- package/scripts/version-and-commit.mjs +237 -0
- package/src/bin/cli.js +670 -0
- package/src/lib/args-parser.js +259 -0
- package/src/lib/isolation.js +419 -0
- package/src/lib/substitution.js +323 -0
- package/src/lib/substitutions.lino +308 -0
- package/test/args-parser.test.js +389 -0
- package/test/isolation.test.js +248 -0
- 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
|
+
});
|