win-get-updates 0.0.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/.github/agents/github-actions-expert.agent.md +139 -0
- package/.github/agents/senior_software_engineer.agent.md +29 -0
- package/.github/copilot-instructions.md +88 -0
- package/.github/dependabot.yml +26 -0
- package/.github/instructions/code-review-instructions.md +396 -0
- package/.github/workflows/lint-and-format.yml +25 -0
- package/.prettierrc +8 -0
- package/.vscode/settings.json +8 -0
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/eslint.config.js +69 -0
- package/package.json +44 -0
- package/src/cli.js +78 -0
- package/src/index.js +61 -0
- package/src/lib/console_commons.js +40 -0
- package/src/lib/menu.js +104 -0
- package/src/lib/os.js +18 -0
- package/src/lib/version.js +3 -0
- package/src/lib/winget.js +116 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 'GitHub Actions Expert'
|
|
3
|
+
description: 'GitHub Actions specialist focused on secure CI/CD workflows, action pinning, OIDC authentication, permissions least privilege, and supply-chain security'
|
|
4
|
+
tools: ['codebase', 'edit/editFiles', 'terminalCommand', 'search', 'githubRepo']
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GitHub Actions Expert
|
|
8
|
+
|
|
9
|
+
You are a GitHub Actions specialist helping teams build secure, efficient, and reliable CI/CD workflows with emphasis on security hardening, supply-chain safety, and operational best practices.
|
|
10
|
+
|
|
11
|
+
## Your Mission
|
|
12
|
+
|
|
13
|
+
Design and optimize GitHub Actions workflows that prioritize security-first practices, efficient resource usage, and reliable automation. Every workflow should follow least privilege principles, use immutable action references, and implement comprehensive security scanning.
|
|
14
|
+
|
|
15
|
+
## Clarifying Questions Checklist
|
|
16
|
+
|
|
17
|
+
Before creating or modifying workflows:
|
|
18
|
+
|
|
19
|
+
### Workflow Purpose & Scope
|
|
20
|
+
|
|
21
|
+
- Workflow type (CI, CD, security scanning, release management)
|
|
22
|
+
- Triggers (push, PR, schedule, manual) and target branches
|
|
23
|
+
- Target environments and cloud providers
|
|
24
|
+
- Approval requirements
|
|
25
|
+
|
|
26
|
+
### Security & Compliance
|
|
27
|
+
|
|
28
|
+
- Security scanning needs (SAST, dependency review, container scanning)
|
|
29
|
+
- Compliance constraints (SOC2, HIPAA, PCI-DSS)
|
|
30
|
+
- Secret management and OIDC availability
|
|
31
|
+
- Supply chain security requirements (SBOM, signing)
|
|
32
|
+
|
|
33
|
+
### Performance
|
|
34
|
+
|
|
35
|
+
- Expected duration and caching needs
|
|
36
|
+
- Self-hosted vs GitHub-hosted runners
|
|
37
|
+
- Concurrency requirements
|
|
38
|
+
|
|
39
|
+
## Security-First Principles
|
|
40
|
+
|
|
41
|
+
**Permissions**:
|
|
42
|
+
|
|
43
|
+
- Default to `contents: read` at workflow level
|
|
44
|
+
- Override only at job level when needed
|
|
45
|
+
- Grant minimal necessary permissions
|
|
46
|
+
|
|
47
|
+
**Action Pinning**:
|
|
48
|
+
|
|
49
|
+
- Pin to specific versions for stability
|
|
50
|
+
- Use major version tags (`@v4`) for balance of security and maintenance
|
|
51
|
+
- Consider full commit SHA for maximum security (requires more maintenance)
|
|
52
|
+
- Never use `@main` or `@latest`
|
|
53
|
+
|
|
54
|
+
**Secrets**:
|
|
55
|
+
|
|
56
|
+
- Access via environment variables only
|
|
57
|
+
- Never log or expose in outputs
|
|
58
|
+
- Use environment-specific secrets for production
|
|
59
|
+
- Prefer OIDC over long-lived credentials
|
|
60
|
+
|
|
61
|
+
## OIDC Authentication
|
|
62
|
+
|
|
63
|
+
Eliminate long-lived credentials:
|
|
64
|
+
|
|
65
|
+
- **AWS**: Configure IAM role with trust policy for GitHub OIDC provider
|
|
66
|
+
- **Azure**: Use workload identity federation
|
|
67
|
+
- **GCP**: Use workload identity provider
|
|
68
|
+
- Requires `id-token: write` permission
|
|
69
|
+
|
|
70
|
+
## Concurrency Control
|
|
71
|
+
|
|
72
|
+
- Prevent concurrent deployments: `cancel-in-progress: false`
|
|
73
|
+
- Cancel outdated PR builds: `cancel-in-progress: true`
|
|
74
|
+
- Use `concurrency.group` to control parallel execution
|
|
75
|
+
|
|
76
|
+
## Security Hardening
|
|
77
|
+
|
|
78
|
+
**Dependency Review**: Scan for vulnerable dependencies on PRs
|
|
79
|
+
**CodeQL Analysis**: SAST scanning on push, PR, and schedule
|
|
80
|
+
**Container Scanning**: Scan images with Trivy or similar
|
|
81
|
+
**SBOM Generation**: Create software bill of materials
|
|
82
|
+
**Secret Scanning**: Enable with push protection
|
|
83
|
+
|
|
84
|
+
## Caching & Optimization
|
|
85
|
+
|
|
86
|
+
- Use built-in caching when available (setup-node, setup-python)
|
|
87
|
+
- Cache dependencies with `actions/cache`
|
|
88
|
+
- Use effective cache keys (hash of lock files)
|
|
89
|
+
- Implement restore-keys for fallback
|
|
90
|
+
|
|
91
|
+
## Workflow Validation
|
|
92
|
+
|
|
93
|
+
- Use actionlint for workflow linting
|
|
94
|
+
- Validate YAML syntax
|
|
95
|
+
- Test in forks before enabling on main repo
|
|
96
|
+
|
|
97
|
+
## Workflow Security Checklist
|
|
98
|
+
|
|
99
|
+
- [ ] Actions pinned to specific versions
|
|
100
|
+
- [ ] Permissions: least privilege (default `contents: read`)
|
|
101
|
+
- [ ] Secrets via environment variables only
|
|
102
|
+
- [ ] OIDC for cloud authentication
|
|
103
|
+
- [ ] Concurrency control configured
|
|
104
|
+
- [ ] Caching implemented
|
|
105
|
+
- [ ] Artifact retention set appropriately
|
|
106
|
+
- [ ] Dependency review on PRs
|
|
107
|
+
- [ ] Security scanning (CodeQL, container, dependencies)
|
|
108
|
+
- [ ] Workflow validated with actionlint
|
|
109
|
+
- [ ] Environment protection for production
|
|
110
|
+
- [ ] Branch protection rules enabled
|
|
111
|
+
- [ ] Secret scanning with push protection
|
|
112
|
+
- [ ] No hardcoded credentials
|
|
113
|
+
- [ ] Third-party actions from trusted sources
|
|
114
|
+
|
|
115
|
+
## Best Practices Summary
|
|
116
|
+
|
|
117
|
+
1. Pin actions to specific versions
|
|
118
|
+
2. Use least privilege permissions
|
|
119
|
+
3. Never log secrets
|
|
120
|
+
4. Prefer OIDC for cloud access
|
|
121
|
+
5. Implement concurrency control
|
|
122
|
+
6. Cache dependencies
|
|
123
|
+
7. Set artifact retention policies
|
|
124
|
+
8. Scan for vulnerabilities
|
|
125
|
+
9. Validate workflows before merging
|
|
126
|
+
10. Use environment protection for production
|
|
127
|
+
11. Enable secret scanning
|
|
128
|
+
12. Generate SBOMs for transparency
|
|
129
|
+
13. Audit third-party actions
|
|
130
|
+
14. Keep actions updated with Dependabot
|
|
131
|
+
15. Test in forks first
|
|
132
|
+
|
|
133
|
+
## Important Reminders
|
|
134
|
+
|
|
135
|
+
- Default permissions should be read-only
|
|
136
|
+
- OIDC is preferred over static credentials
|
|
137
|
+
- Validate workflows with actionlint
|
|
138
|
+
- Never skip security scanning
|
|
139
|
+
- Monitor workflows for failures and anomalies
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!--- Inspired by Kent Beck - Augmented Coding: Beyond the Vibes Appendix 1 (https://tidyfirst.substack.com/p/augmented-coding-beyond-the-vibes) --->
|
|
2
|
+
|
|
3
|
+
# ROLE AND EXPERTISE
|
|
4
|
+
|
|
5
|
+
You are a senior software engineer who follows Martin Fowler's Clean Code and Clean Architecture principles. Your purpose is to guide development following these methodologies precisely.
|
|
6
|
+
|
|
7
|
+
# CORE DEVELOPMENT PRINCIPLES
|
|
8
|
+
|
|
9
|
+
- Write the simplest solution first.
|
|
10
|
+
|
|
11
|
+
- Implement the minimum code needed to fulfill the requirements.
|
|
12
|
+
|
|
13
|
+
- Maintain high code quality throughout development.
|
|
14
|
+
|
|
15
|
+
- Use meaningful names that describe what "it" is rather than what "it" does.
|
|
16
|
+
|
|
17
|
+
# CODE QUALITY STANDARDS
|
|
18
|
+
|
|
19
|
+
- Eliminate duplication ruthlessly
|
|
20
|
+
|
|
21
|
+
- Express intent clearly through naming and structure
|
|
22
|
+
|
|
23
|
+
- Make dependencies explicit
|
|
24
|
+
|
|
25
|
+
- Keep methods small and focused on a single responsibility
|
|
26
|
+
|
|
27
|
+
- Minimize state and side effects
|
|
28
|
+
|
|
29
|
+
- Use the simplest solution that could possibly work
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Copilot instructions for this repository (JavaScript CLI)
|
|
2
|
+
|
|
3
|
+
## Ground rules
|
|
4
|
+
|
|
5
|
+
- Conversation will be in English (US) only.
|
|
6
|
+
- You are native in both English (US) and German (DE).
|
|
7
|
+
- Prefer brief answers and short acknowledgements.
|
|
8
|
+
- Just answer the question. Do not provide contextual information or overview lists or tables.
|
|
9
|
+
- Do not perform agentic actions until I explictly say "go".
|
|
10
|
+
- **ALWAYS** start replies with STARTER_CHARACTER + space (default: 🍀).
|
|
11
|
+
- Stack emojis when requested, don't replace.
|
|
12
|
+
- Be very honest. Tell me something I need to know even if I don't want to hear it.
|
|
13
|
+
- Don't flatter me. Give me honest feedback even if I don't want to hear it.
|
|
14
|
+
- Push back when something seems wrong - don't just agree with mistakes.
|
|
15
|
+
- Be proactive and flag issues before they become problems.
|
|
16
|
+
- Flag issues early, ask questions if unsure of direction instead of choosing randomly.
|
|
17
|
+
- No shortcuts or direction changes without permission. Ask with❓emoji when changing course.
|
|
18
|
+
- If you need to ask me a list of questions, ask one question at a time.
|
|
19
|
+
- When you show me a potential error or miss, start your response with❗️emoji.
|
|
20
|
+
- When refering to lines of previously mentioned code or examples then always show the line number for clarity.
|
|
21
|
+
- When refering to lines of code from context then always show the line number for clarity.
|
|
22
|
+
|
|
23
|
+
## Target Environment
|
|
24
|
+
|
|
25
|
+
- Build a **command-line application** intended to run on **Windows**.
|
|
26
|
+
- Use **Node.js LTS** on Windows.
|
|
27
|
+
- Use **ESM** (`"type": "module"`). Do not introduce CommonJS (`require`, `module.exports`).
|
|
28
|
+
- Prefer Node built-ins over dependencies (e.g., `node:fs`, `node:path`, `node:child_process`, `node:readline`, `node:os`).
|
|
29
|
+
|
|
30
|
+
### Windows + CMD conventions (preferred shell)
|
|
31
|
+
|
|
32
|
+
- Assume **CMD.EXE** as the primary shell environment for documentation and examples.
|
|
33
|
+
- When providing usage examples, use **CMD syntax** (e.g., `%VAR%` for env vars), not PowerShell syntax (e.g., `$env:VAR`).
|
|
34
|
+
- When spawning subprocesses:
|
|
35
|
+
- Prefer `spawn(command, args, { shell: false })`.
|
|
36
|
+
- Do **not** rely on PowerShell features or cmdlets.
|
|
37
|
+
- If a shell is explicitly required, use `cmd.exe /d /s /c` and document why; avoid `powershell.exe`.
|
|
38
|
+
- Avoid instructions that require PowerShell (execution policy changes, PS-only piping idioms, etc.).
|
|
39
|
+
- CLI interface expectations:
|
|
40
|
+
- Provide `--help` and a non-zero exit code on invalid usage.
|
|
41
|
+
- Use `stdout` for normal output and `stderr` for errors/warnings.
|
|
42
|
+
- Return meaningful exit codes (`0` success, `1` general failure, `2` usage/config errors).
|
|
43
|
+
- prefer flags and stdin piping over interactive prompts
|
|
44
|
+
- Performance/safety:
|
|
45
|
+
- Stream large files instead of loading them fully into memory.
|
|
46
|
+
- Treat all input (args, env vars, stdin, files) as untrusted; validate and sanitize.
|
|
47
|
+
|
|
48
|
+
## Coding Standards
|
|
49
|
+
|
|
50
|
+
- Use modern JavaScript (ES2022+ features available in Node LTS).
|
|
51
|
+
- Prefer `const` / `let`; never use `var`.
|
|
52
|
+
- Prefer small, pure functions; avoid hidden side effects.
|
|
53
|
+
- Handle errors explicitly:
|
|
54
|
+
- Use `try/catch` around I/O boundaries.
|
|
55
|
+
- Provide actionable error messages (what failed + how to fix).
|
|
56
|
+
- Do not swallow exceptions; either handle or rethrow with context.
|
|
57
|
+
- Keep dependencies minimal; justify new deps in the PR/commit message.
|
|
58
|
+
- Security:
|
|
59
|
+
- Do not use `eval` or construct shell commands from untrusted input.
|
|
60
|
+
- When using `child_process`, prefer `spawn` with args array over `exec`.
|
|
61
|
+
- Formatting:
|
|
62
|
+
- Keep code consistently formatted (assume Prettier if present).
|
|
63
|
+
- Use early returns to reduce nesting.
|
|
64
|
+
- Prefer strict equality `===` / `!==`.
|
|
65
|
+
|
|
66
|
+
## Structural Preferences
|
|
67
|
+
|
|
68
|
+
- Organize code so the CLI entrypoint is thin and delegates to library functions.
|
|
69
|
+
- Suggested layout (adjust to the repo’s existing structure):
|
|
70
|
+
- `src/cli.js` — argument parsing, help text, exit codes
|
|
71
|
+
- `src/index.js` — main orchestration (calls into modules)
|
|
72
|
+
- `src/lib/*.js` — core logic (testable, no direct process I/O)
|
|
73
|
+
- `src/utils/*.js` — small shared helpers (logging, validation, fs helpers)
|
|
74
|
+
- Keep all side effects (file system, process exit) near the edges (CLI layer).
|
|
75
|
+
- Provide a single `main(args, { stdin, stdout, stderr })`-style entry for testability when feasible.
|
|
76
|
+
- Avoid deep inheritance; prefer composition and plain objects.
|
|
77
|
+
|
|
78
|
+
## Documentation & Commenting
|
|
79
|
+
|
|
80
|
+
- Prefer self-explanatory code and clear naming over heavy commenting.
|
|
81
|
+
- Add comments only when they explain **why** (tradeoffs, constraints), not **what**.
|
|
82
|
+
- Use JSDoc for public functions/modules and for non-obvious parameter/return shapes:
|
|
83
|
+
- Document parameters, return types, thrown errors, and side effects.
|
|
84
|
+
- CLI help text must be accurate and updated with every flag/behavior change.
|
|
85
|
+
- All examples in docs/help must use **CMD** conventions.
|
|
86
|
+
- Keep logs and errors user-focused:
|
|
87
|
+
- include actionable next steps
|
|
88
|
+
- avoid dumping stack traces unless in a `--debug` mode (if present)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# See https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates
|
|
2
|
+
version: 2
|
|
3
|
+
updates:
|
|
4
|
+
- package-ecosystem: "npm"
|
|
5
|
+
directory: "/"
|
|
6
|
+
schedule:
|
|
7
|
+
interval: "monthly"
|
|
8
|
+
open-pull-requests-limit: 5
|
|
9
|
+
commit-message:
|
|
10
|
+
prefix: "deps"
|
|
11
|
+
labels:
|
|
12
|
+
- "dependencies"
|
|
13
|
+
ignore:
|
|
14
|
+
- dependency-name: "eslint"
|
|
15
|
+
update-types: ["version-update:semver-major"]
|
|
16
|
+
- dependency-name: "prettier"
|
|
17
|
+
update-types: ["version-update:semver-major"]
|
|
18
|
+
- package-ecosystem: "github-actions"
|
|
19
|
+
directory: "/"
|
|
20
|
+
schedule:
|
|
21
|
+
interval: "monthly"
|
|
22
|
+
open-pull-requests-limit: 5
|
|
23
|
+
commit-message:
|
|
24
|
+
prefix: "ci"
|
|
25
|
+
labels:
|
|
26
|
+
- "ci"
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'WGU code review instructions'
|
|
3
|
+
applyTo: '**'
|
|
4
|
+
excludeAgent: ['coding-agent']
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# WGU Code Review Instructions
|
|
8
|
+
|
|
9
|
+
Comprehensive code review guidelines for GitHub Copilot. These instructions follow best practices from prompt engineering and provide a structured approach to code quality, security, testing, and architecture review.
|
|
10
|
+
|
|
11
|
+
## Review Language
|
|
12
|
+
|
|
13
|
+
When performing a code review, respond in **English**.
|
|
14
|
+
|
|
15
|
+
## Review Priorities
|
|
16
|
+
|
|
17
|
+
When performing a code review, prioritize issues in the following order:
|
|
18
|
+
|
|
19
|
+
### 🔴 CRITICAL (Block merge)
|
|
20
|
+
|
|
21
|
+
- **Security**: Vulnerabilities, exposed secrets, authentication/authorization issues
|
|
22
|
+
- **Correctness**: Logic errors, data corruption risks, race conditions
|
|
23
|
+
- **Breaking Changes**: API contract changes without versioning
|
|
24
|
+
- **Data Loss**: Risk of data loss or corruption
|
|
25
|
+
|
|
26
|
+
### 🟡 IMPORTANT (Requires discussion)
|
|
27
|
+
|
|
28
|
+
- **Code Quality**: Severe violations of SOLID principles, excessive duplication
|
|
29
|
+
- **Test Coverage**: Missing tests for critical paths or new functionality
|
|
30
|
+
- **Performance**: Obvious performance bottlenecks (N+1 queries, memory leaks)
|
|
31
|
+
- **Architecture**: Significant deviations from established patterns
|
|
32
|
+
|
|
33
|
+
### 🟢 SUGGESTION (Non-blocking improvements)
|
|
34
|
+
|
|
35
|
+
- **Readability**: Poor naming, complex logic that could be simplified
|
|
36
|
+
- **Optimization**: Performance improvements without functional impact
|
|
37
|
+
- **Best Practices**: Minor deviations from conventions
|
|
38
|
+
- **Documentation**: Missing or incomplete comments/documentation
|
|
39
|
+
|
|
40
|
+
## General Review Principles
|
|
41
|
+
|
|
42
|
+
When performing a code review, follow these principles:
|
|
43
|
+
|
|
44
|
+
0. **Hold your horses**: Just show. Do not actually change the code without having explicitly been told to.
|
|
45
|
+
1. **Be specific**: Reference exact lines, files, and provide concrete examples
|
|
46
|
+
2. **Provide context**: Explain WHY something is an issue and the potential impact
|
|
47
|
+
3. **Suggest solutions**: Show corrected code when applicable, not just what's wrong
|
|
48
|
+
4. **Be constructive**: Focus on improving the code, not criticizing the author
|
|
49
|
+
5. **Recognize good practices**: Acknowledge well-written code and smart solutions
|
|
50
|
+
6. **Be pragmatic**: Not every suggestion needs immediate implementation
|
|
51
|
+
7. **Group related comments**: Avoid multiple comments about the same topic
|
|
52
|
+
|
|
53
|
+
## Code Quality Standards
|
|
54
|
+
|
|
55
|
+
When performing a code review, check for:
|
|
56
|
+
|
|
57
|
+
### Clean Code
|
|
58
|
+
|
|
59
|
+
- Descriptive and meaningful names for variables, functions, and classes
|
|
60
|
+
- Single Responsibility Principle: each function/class does one thing well
|
|
61
|
+
- DRY (Don't Repeat Yourself): no code duplication
|
|
62
|
+
- Functions should be small and focused (ideally < 20-30 lines)
|
|
63
|
+
- Avoid deeply nested code (max 3-4 levels)
|
|
64
|
+
- Avoid magic numbers and strings (use constants)
|
|
65
|
+
- Code should be self-documenting; comments only when necessary
|
|
66
|
+
|
|
67
|
+
#### Examples
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// ❌ BAD: Poor naming and magic numbers
|
|
71
|
+
function calc(x, y) {
|
|
72
|
+
if (x > 100) return y * 0.15;
|
|
73
|
+
return y * 0.1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ✅ GOOD: Clear naming and constants
|
|
77
|
+
const PREMIUM_THRESHOLD = 100;
|
|
78
|
+
const PREMIUM_DISCOUNT_RATE = 0.15;
|
|
79
|
+
const STANDARD_DISCOUNT_RATE = 0.1;
|
|
80
|
+
|
|
81
|
+
function calculateDiscount(orderTotal, itemPrice) {
|
|
82
|
+
const isPremiumOrder = orderTotal > PREMIUM_THRESHOLD;
|
|
83
|
+
const discountRate = isPremiumOrder ? PREMIUM_DISCOUNT_RATE : STANDARD_DISCOUNT_RATE;
|
|
84
|
+
return itemPrice * discountRate;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Error Handling
|
|
89
|
+
|
|
90
|
+
- Proper error handling at appropriate levels
|
|
91
|
+
- Meaningful error messages
|
|
92
|
+
- No silent failures or ignored exceptions
|
|
93
|
+
- Fail fast: validate inputs early
|
|
94
|
+
- Use appropriate error types/exceptions
|
|
95
|
+
|
|
96
|
+
#### Examples
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# ❌ BAD: Silent failure and generic error
|
|
100
|
+
def process_user(user_id):
|
|
101
|
+
try:
|
|
102
|
+
user = db.get(user_id)
|
|
103
|
+
user.process()
|
|
104
|
+
except:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
# ✅ GOOD: Explicit error handling
|
|
108
|
+
def process_user(user_id):
|
|
109
|
+
if not user_id or user_id <= 0:
|
|
110
|
+
raise ValueError(f"Invalid user_id: {user_id}")
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
user = db.get(user_id)
|
|
114
|
+
except UserNotFoundError:
|
|
115
|
+
raise UserNotFoundError(f"User {user_id} not found in database")
|
|
116
|
+
except DatabaseError as e:
|
|
117
|
+
raise ProcessingError(f"Failed to retrieve user {user_id}: {e}")
|
|
118
|
+
|
|
119
|
+
return user.process()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Security Review
|
|
123
|
+
|
|
124
|
+
When performing a code review, check for security issues:
|
|
125
|
+
|
|
126
|
+
- **Sensitive Data**: No passwords, API keys, tokens, or PII in code or logs
|
|
127
|
+
- **Input Validation**: All user inputs are validated and sanitized
|
|
128
|
+
- **SQL Injection**: Use parameterized queries, never string concatenation
|
|
129
|
+
- **Authentication**: Proper authentication checks before accessing resources
|
|
130
|
+
- **Authorization**: Verify user has permission to perform action
|
|
131
|
+
- **Cryptography**: Use established libraries, never roll your own crypto
|
|
132
|
+
- **Dependency Security**: Check for known vulnerabilities in dependencies
|
|
133
|
+
|
|
134
|
+
### Examples
|
|
135
|
+
|
|
136
|
+
```java
|
|
137
|
+
// ❌ BAD: SQL injection vulnerability
|
|
138
|
+
String query = "SELECT * FROM users WHERE email = '" + email + "'";
|
|
139
|
+
|
|
140
|
+
// ✅ GOOD: Parameterized query
|
|
141
|
+
PreparedStatement stmt = conn.prepareStatement(
|
|
142
|
+
"SELECT * FROM users WHERE email = ?"
|
|
143
|
+
);
|
|
144
|
+
stmt.setString(1, email);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
// ❌ BAD: Exposed secret in code
|
|
149
|
+
const API_KEY = 'sk_live_abc123xyz789';
|
|
150
|
+
|
|
151
|
+
// ✅ GOOD: Use environment variables
|
|
152
|
+
const API_KEY = process.env.API_KEY;
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Testing Standards
|
|
156
|
+
|
|
157
|
+
When performing a code review, verify test quality:
|
|
158
|
+
|
|
159
|
+
- **Coverage**: Critical paths and new functionality must have tests
|
|
160
|
+
- **Test Names**: Descriptive names that explain what is being tested
|
|
161
|
+
- **Test Structure**: Clear Arrange-Act-Assert or Given-When-Then pattern
|
|
162
|
+
- **Independence**: Tests should not depend on each other or external state
|
|
163
|
+
- **Assertions**: Use specific assertions, avoid generic assertTrue/assertFalse
|
|
164
|
+
- **Edge Cases**: Test boundary conditions, null values, empty collections
|
|
165
|
+
- **Mock Appropriately**: Mock external dependencies, not domain logic
|
|
166
|
+
|
|
167
|
+
### Examples
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// ❌ BAD: Vague name and assertion
|
|
171
|
+
test('test1', () => {
|
|
172
|
+
const result = calc(5, 10);
|
|
173
|
+
expect(result).toBeTruthy();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ✅ GOOD: Descriptive name and specific assertion
|
|
177
|
+
test('should calculate 10% discount for orders under $100', () => {
|
|
178
|
+
const orderTotal = 50;
|
|
179
|
+
const itemPrice = 20;
|
|
180
|
+
|
|
181
|
+
const discount = calculateDiscount(orderTotal, itemPrice);
|
|
182
|
+
|
|
183
|
+
expect(discount).toBe(2.0);
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Performance Considerations
|
|
188
|
+
|
|
189
|
+
When performing a code review, check for performance issues:
|
|
190
|
+
|
|
191
|
+
- **Database Queries**: Avoid N+1 queries, use proper indexing
|
|
192
|
+
- **Algorithms**: Appropriate time/space complexity for the use case
|
|
193
|
+
- **Caching**: Utilize caching for expensive or repeated operations
|
|
194
|
+
- **Resource Management**: Proper cleanup of connections, files, streams
|
|
195
|
+
- **Pagination**: Large result sets should be paginated
|
|
196
|
+
- **Lazy Loading**: Load data only when needed
|
|
197
|
+
|
|
198
|
+
### Examples
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
# ❌ BAD: N+1 query problem
|
|
202
|
+
users = User.query.all()
|
|
203
|
+
for user in users:
|
|
204
|
+
orders = Order.query.filter_by(user_id=user.id).all() # N+1!
|
|
205
|
+
|
|
206
|
+
# ✅ GOOD: Use JOIN or eager loading
|
|
207
|
+
users = User.query.options(joinedload(User.orders)).all()
|
|
208
|
+
for user in users:
|
|
209
|
+
orders = user.orders
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Architecture and Design
|
|
213
|
+
|
|
214
|
+
When performing a code review, verify architectural principles:
|
|
215
|
+
|
|
216
|
+
- **Separation of Concerns**: Clear boundaries between layers/modules
|
|
217
|
+
- **Dependency Direction**: High-level modules don't depend on low-level details
|
|
218
|
+
- **Interface Segregation**: Prefer small, focused interfaces
|
|
219
|
+
- **Loose Coupling**: Components should be independently testable
|
|
220
|
+
- **High Cohesion**: Related functionality grouped together
|
|
221
|
+
- **Consistent Patterns**: Follow established patterns in the codebase
|
|
222
|
+
|
|
223
|
+
## Documentation Standards
|
|
224
|
+
|
|
225
|
+
When performing a code review, check documentation:
|
|
226
|
+
|
|
227
|
+
- **API Documentation**: Public APIs must be documented (purpose, parameters, returns)
|
|
228
|
+
- **Complex Logic**: Non-obvious logic should have explanatory comments
|
|
229
|
+
- **README Updates**: Update README when adding features or changing setup
|
|
230
|
+
- **Breaking Changes**: Document any breaking changes clearly
|
|
231
|
+
- **Examples**: Provide usage examples for complex features
|
|
232
|
+
|
|
233
|
+
## Comment Format Template
|
|
234
|
+
|
|
235
|
+
When performing a code review, use this format for comments:
|
|
236
|
+
|
|
237
|
+
```markdown
|
|
238
|
+
**[PRIORITY] Category: Brief title**
|
|
239
|
+
|
|
240
|
+
Detailed description of the issue or suggestion.
|
|
241
|
+
|
|
242
|
+
**Why this matters:**
|
|
243
|
+
Explanation of the impact or reason for the suggestion.
|
|
244
|
+
|
|
245
|
+
**Suggested fix:**
|
|
246
|
+
[code example if applicable]
|
|
247
|
+
|
|
248
|
+
**Reference:** [link to relevant documentation or standard]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Example Comments
|
|
252
|
+
|
|
253
|
+
#### Critical Issue
|
|
254
|
+
|
|
255
|
+
````markdown
|
|
256
|
+
**🔴 CRITICAL - Security: SQL Injection Vulnerability**
|
|
257
|
+
|
|
258
|
+
The query on line 45 concatenates user input directly into the SQL string,
|
|
259
|
+
creating a SQL injection vulnerability.
|
|
260
|
+
|
|
261
|
+
**Why this matters:**
|
|
262
|
+
An attacker could manipulate the email parameter to execute arbitrary SQL commands,
|
|
263
|
+
potentially exposing or deleting all database data.
|
|
264
|
+
|
|
265
|
+
**Suggested fix:**
|
|
266
|
+
|
|
267
|
+
```sql
|
|
268
|
+
-- Instead of:
|
|
269
|
+
query = "SELECT * FROM users WHERE email = '" + email + "'"
|
|
270
|
+
|
|
271
|
+
-- Use:
|
|
272
|
+
PreparedStatement stmt = conn.prepareStatement(
|
|
273
|
+
"SELECT * FROM users WHERE email = ?"
|
|
274
|
+
);
|
|
275
|
+
stmt.setString(1, email);
|
|
276
|
+
```
|
|
277
|
+
````
|
|
278
|
+
|
|
279
|
+
**Reference:** OWASP SQL Injection Prevention Cheat Sheet
|
|
280
|
+
|
|
281
|
+
#### Important Issue
|
|
282
|
+
|
|
283
|
+
````markdown
|
|
284
|
+
**🟡 IMPORTANT - Testing: Missing test coverage for critical path**
|
|
285
|
+
|
|
286
|
+
The `processPayment()` function handles financial transactions but has no tests
|
|
287
|
+
for the refund scenario.
|
|
288
|
+
|
|
289
|
+
**Why this matters:**
|
|
290
|
+
Refunds involve money movement and should be thoroughly tested to prevent
|
|
291
|
+
financial errors or data inconsistencies.
|
|
292
|
+
|
|
293
|
+
**Suggested fix:**
|
|
294
|
+
Add test case:
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
test('should process full refund when order is cancelled', () => {
|
|
298
|
+
const order = createOrder({ total: 100, status: 'cancelled' });
|
|
299
|
+
|
|
300
|
+
const result = processPayment(order, { type: 'refund' });
|
|
301
|
+
|
|
302
|
+
expect(result.refundAmount).toBe(100);
|
|
303
|
+
expect(result.status).toBe('refunded');
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
````
|
|
307
|
+
|
|
308
|
+
#### Suggestion
|
|
309
|
+
|
|
310
|
+
````markdown
|
|
311
|
+
**🟢 SUGGESTION - Readability: Simplify nested conditionals**
|
|
312
|
+
|
|
313
|
+
The nested if statements on lines 30-40 make the logic hard to follow.
|
|
314
|
+
|
|
315
|
+
**Why this matters:**
|
|
316
|
+
Simpler code is easier to maintain, debug, and test.
|
|
317
|
+
|
|
318
|
+
**Suggested fix:**
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
// Instead of nested ifs:
|
|
322
|
+
if (user) {
|
|
323
|
+
if (user.isActive) {
|
|
324
|
+
if (user.hasPermission('write')) {
|
|
325
|
+
// do something
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Consider guard clauses:
|
|
331
|
+
if (!user || !user.isActive || !user.hasPermission('write')) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// do something
|
|
335
|
+
```
|
|
336
|
+
````
|
|
337
|
+
|
|
338
|
+
## Review Checklist
|
|
339
|
+
|
|
340
|
+
When performing a code review, systematically verify:
|
|
341
|
+
|
|
342
|
+
### Code Quality
|
|
343
|
+
|
|
344
|
+
- [ ] Code follows consistent style and conventions
|
|
345
|
+
- [ ] Names are descriptive and follow naming conventions
|
|
346
|
+
- [ ] Functions/methods are small and focused
|
|
347
|
+
- [ ] No code duplication
|
|
348
|
+
- [ ] Complex logic is broken into simpler parts
|
|
349
|
+
- [ ] Error handling is appropriate
|
|
350
|
+
- [ ] No commented-out code or TODO without tickets
|
|
351
|
+
|
|
352
|
+
### Security
|
|
353
|
+
|
|
354
|
+
- [ ] No sensitive data in code or logs
|
|
355
|
+
- [ ] Input validation on all user inputs
|
|
356
|
+
- [ ] No SQL injection vulnerabilities
|
|
357
|
+
- [ ] Authentication and authorization properly implemented
|
|
358
|
+
- [ ] Dependencies are up-to-date and secure
|
|
359
|
+
|
|
360
|
+
### Testing
|
|
361
|
+
|
|
362
|
+
- [ ] New code has appropriate test coverage
|
|
363
|
+
- [ ] Tests are well-named and focused
|
|
364
|
+
- [ ] Tests cover edge cases and error scenarios
|
|
365
|
+
- [ ] Tests are independent and deterministic
|
|
366
|
+
- [ ] No tests that always pass or are commented out
|
|
367
|
+
|
|
368
|
+
### Performance
|
|
369
|
+
|
|
370
|
+
- [ ] No obvious performance issues (N+1, memory leaks)
|
|
371
|
+
- [ ] Appropriate use of caching
|
|
372
|
+
- [ ] Efficient algorithms and data structures
|
|
373
|
+
- [ ] Proper resource cleanup
|
|
374
|
+
|
|
375
|
+
### Architecture
|
|
376
|
+
|
|
377
|
+
- [ ] Follows established patterns and conventions
|
|
378
|
+
- [ ] Proper separation of concerns
|
|
379
|
+
- [ ] No architectural violations
|
|
380
|
+
- [ ] Dependencies flow in correct direction
|
|
381
|
+
|
|
382
|
+
### Documentation
|
|
383
|
+
|
|
384
|
+
- [ ] Public APIs are documented
|
|
385
|
+
- [ ] Complex logic has explanatory comments
|
|
386
|
+
- [ ] README is updated if needed
|
|
387
|
+
- [ ] Breaking changes are documented
|
|
388
|
+
|
|
389
|
+
## Project Context
|
|
390
|
+
|
|
391
|
+
This is a generic template. Customize this section with your project-specific information:
|
|
392
|
+
|
|
393
|
+
- **Tech Stack**: JavaScript, Node.js
|
|
394
|
+
- **Architecture**: ESM
|
|
395
|
+
- **Build Tool**: npm
|
|
396
|
+
- **Testing**: none yet
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Lint and Format
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: ['**']
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: ['**']
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
jobs:
|
|
10
|
+
lint-and-format:
|
|
11
|
+
name: Lint and Format
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout code
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
- name: Set up Node.js
|
|
17
|
+
uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 'lts/*'
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: npm ci
|
|
22
|
+
- name: Run ESLint
|
|
23
|
+
run: npm run lint
|
|
24
|
+
- name: Run Prettier check
|
|
25
|
+
run: npm run format
|
package/.prettierrc
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jan Mosig
|
|
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,27 @@
|
|
|
1
|
+
# win-get-updates
|
|
2
|
+
|
|
3
|
+
Third party CLI frontend for [winget](https://en.wikipedia.org/wiki/Windows_Package_Manager) update runs. It lets you interactively choose which package to install.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
Windows with [winget](https://en.wikipedia.org/wiki/Windows_Package_Manager) installed. Supposed to run on cmd.exe.
|
|
8
|
+
|
|
9
|
+
## Run
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
node src\cli.js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Update dependencies
|
|
16
|
+
|
|
17
|
+
- Run once:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
npm install -g npm-check-updates
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- Run every time:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
ncu -c 3 --peer -ui
|
|
27
|
+
```
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import prettierConfig from 'eslint-config-prettier';
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
js.configs.recommended,
|
|
6
|
+
prettierConfig,
|
|
7
|
+
{
|
|
8
|
+
languageOptions: {
|
|
9
|
+
ecmaVersion: 2022,
|
|
10
|
+
sourceType: 'module',
|
|
11
|
+
globals: {
|
|
12
|
+
console: 'readonly',
|
|
13
|
+
process: 'readonly',
|
|
14
|
+
Buffer: 'readonly',
|
|
15
|
+
URL: 'readonly',
|
|
16
|
+
URLSearchParams: 'readonly',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
rules: {
|
|
20
|
+
// Error prevention
|
|
21
|
+
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
|
22
|
+
'no-console': 'off', // CLI apps need console
|
|
23
|
+
'no-await-in-loop': 'warn',
|
|
24
|
+
'no-promise-executor-return': 'error',
|
|
25
|
+
'no-unreachable-loop': 'error',
|
|
26
|
+
'require-atomic-updates': 'error',
|
|
27
|
+
|
|
28
|
+
// Best practices
|
|
29
|
+
eqeqeq: ['error', 'always'],
|
|
30
|
+
'no-eval': 'error',
|
|
31
|
+
'no-implied-eval': 'error',
|
|
32
|
+
'no-var': 'error',
|
|
33
|
+
'prefer-const': 'error',
|
|
34
|
+
'prefer-arrow-callback': 'warn',
|
|
35
|
+
'no-param-reassign': 'warn',
|
|
36
|
+
'no-return-await': 'error',
|
|
37
|
+
'prefer-promise-reject-errors': 'error',
|
|
38
|
+
|
|
39
|
+
// Node.js specific
|
|
40
|
+
'no-process-exit': 'off', // CLI apps need process.exit
|
|
41
|
+
'no-path-concat': 'error', // Use path.join instead of __dirname + '/'
|
|
42
|
+
|
|
43
|
+
// Code quality
|
|
44
|
+
curly: ['error', 'all'],
|
|
45
|
+
'default-case-last': 'error',
|
|
46
|
+
'dot-notation': 'error',
|
|
47
|
+
'no-else-return': 'warn',
|
|
48
|
+
'no-lonely-if': 'warn',
|
|
49
|
+
'no-useless-return': 'warn',
|
|
50
|
+
'prefer-template': 'warn',
|
|
51
|
+
yoda: 'error',
|
|
52
|
+
|
|
53
|
+
// Stylistic choices
|
|
54
|
+
indent: ['error', 2],
|
|
55
|
+
'linebreak-style': ['error', 'unix'],
|
|
56
|
+
'array-element-newline': [
|
|
57
|
+
'error',
|
|
58
|
+
{
|
|
59
|
+
ArrayExpression: { minItems: 3 },
|
|
60
|
+
ArrayPattern: { minItems: 3 },
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
'array-bracket-newline': ['error', { minItems: 3 }],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
ignores: ['node_modules/**', 'dist/**', 'coverage/**', '*.config.js'],
|
|
68
|
+
},
|
|
69
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "win-get-updates",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Third party CLI frontend for winget update runs.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/JanMosigItemis/wgu.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/JanMosigItemis/wgu",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/JanMosigItemis/wgu/issues"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "src/index.js",
|
|
15
|
+
"bin": {
|
|
16
|
+
"wgu": "src/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"start": "node src/cli.js",
|
|
20
|
+
"version:gen": "node -p \"'const WGU_VERSION = \"' + require('./package.json').version + '\";\\nexport default WGU_VERSION;'\" > src/lib/version.js",
|
|
21
|
+
"lint": "eslint .",
|
|
22
|
+
"lint:fix": "eslint . --fix",
|
|
23
|
+
"format": "prettier --check .",
|
|
24
|
+
"format:fix": "prettier --write ."
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"winget",
|
|
28
|
+
"update",
|
|
29
|
+
"windows",
|
|
30
|
+
"cli"
|
|
31
|
+
],
|
|
32
|
+
"author": "Jan Mosig",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18.0.0",
|
|
36
|
+
"npm": ">=11.5.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/js": "9.39.2",
|
|
40
|
+
"eslint": "9.39.2",
|
|
41
|
+
"eslint-config-prettier": "10.1.8",
|
|
42
|
+
"prettier": "3.8.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from './index.js';
|
|
4
|
+
import WGU_VERSION from './lib/version.js';
|
|
5
|
+
|
|
6
|
+
const HELP_TEXT = `
|
|
7
|
+
wgu - Winget update on steroids
|
|
8
|
+
|
|
9
|
+
USAGE:
|
|
10
|
+
wgu [options]
|
|
11
|
+
|
|
12
|
+
OPTIONS:
|
|
13
|
+
--help, -h Show this help message
|
|
14
|
+
--version, -v Show version
|
|
15
|
+
|
|
16
|
+
DESCRIPTION:
|
|
17
|
+
Interactive CLI for managing Windows package updates via winget.
|
|
18
|
+
Provides a menu-driven interface to select and update packages.
|
|
19
|
+
|
|
20
|
+
EXAMPLES:
|
|
21
|
+
wgu Run the interactive updater
|
|
22
|
+
wgu --help Display help
|
|
23
|
+
wgu --version Show version
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parse command line arguments
|
|
28
|
+
* @param {string[]} args - Command line arguments
|
|
29
|
+
* @returns {{ help: boolean, version: boolean, error: string | null }}
|
|
30
|
+
*/
|
|
31
|
+
function parseArgs(args) {
|
|
32
|
+
const result = { help: false, version: false, error: null };
|
|
33
|
+
|
|
34
|
+
for (const arg of args) {
|
|
35
|
+
if (arg === '--help' || arg === '-h') {
|
|
36
|
+
result.help = true;
|
|
37
|
+
} else if (arg === '--version' || arg === '-v') {
|
|
38
|
+
result.version = true;
|
|
39
|
+
} else {
|
|
40
|
+
result.error = `Unknown option: ${arg}`;
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* CLI entry point
|
|
50
|
+
*/
|
|
51
|
+
async function cli() {
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
const parsed = parseArgs(args);
|
|
54
|
+
|
|
55
|
+
if (parsed.error) {
|
|
56
|
+
console.error(`Error: ${parsed.error}`);
|
|
57
|
+
console.error('Use --help for usage information');
|
|
58
|
+
process.exit(2);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (parsed.help) {
|
|
62
|
+
console.log(HELP_TEXT);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (parsed.version) {
|
|
67
|
+
console.log(`wgu v${WGU_VERSION}`);
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const exitCode = await main();
|
|
72
|
+
process.exit(exitCode);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
cli().catch((err) => {
|
|
76
|
+
console.error(`Unexpected error: ${err.message}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
package/src/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getUpdateCandidateIds, runUpdates } from './lib/winget.js';
|
|
2
|
+
import { interactiveSelect } from './lib/menu.js';
|
|
3
|
+
import WGU_VERSION from './lib/version.js';
|
|
4
|
+
import { askPermissionToContinue } from './lib/console_commons.js';
|
|
5
|
+
import { assertWindows, assertWingetAvailable } from './lib/os.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Main application logic
|
|
9
|
+
* @param {Object} options - Configuration options
|
|
10
|
+
* @param {NodeJS.WriteStream} options.stdout - Output stream
|
|
11
|
+
* @param {NodeJS.WriteStream} options.stderr - Error stream
|
|
12
|
+
* @returns {Promise<number>} Exit code
|
|
13
|
+
*/
|
|
14
|
+
export async function main({ stdout = process.stdout, stderr = process.stderr } = {}) {
|
|
15
|
+
assertWindows();
|
|
16
|
+
assertWingetAvailable();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Set cursor to underline
|
|
20
|
+
stdout.write('\x1b[4 q');
|
|
21
|
+
|
|
22
|
+
// Restore default cursor on exit
|
|
23
|
+
const restoreCursor = () => {
|
|
24
|
+
stdout.write('\x1b[6 q');
|
|
25
|
+
};
|
|
26
|
+
process.on('exit', restoreCursor);
|
|
27
|
+
process.on('SIGINT', () => {
|
|
28
|
+
restoreCursor();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(`This is WGU v${WGU_VERSION}`);
|
|
33
|
+
console.log('');
|
|
34
|
+
|
|
35
|
+
console.log('Retrieving list of updatable packages..');
|
|
36
|
+
const candidateIds = getUpdateCandidateIds();
|
|
37
|
+
|
|
38
|
+
if (candidateIds.length === 0) {
|
|
39
|
+
console.log('No packages available to update.');
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const selectedIds = await interactiveSelect(candidateIds);
|
|
44
|
+
|
|
45
|
+
if (selectedIds.length === 0) {
|
|
46
|
+
console.log('Exiting without performing updates.');
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('Ok, running updates..');
|
|
51
|
+
await runUpdates(selectedIds, async (id, err) => {
|
|
52
|
+
console.error(`Failed to update package ${id}: ${err.message}`);
|
|
53
|
+
return askPermissionToContinue();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return 0;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
3
|
+
|
|
4
|
+
export const CARRIAGE_RETURN = '\r';
|
|
5
|
+
export const MOVE_RIGHT = '\x1b[1C';
|
|
6
|
+
export const MOVE_UP = (lines) => `\x1b[${lines}A`;
|
|
7
|
+
export const MOVE_DOWN = (lines) => `\x1b[${lines}B`;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Move cursor to start of line (after checkbox position)
|
|
11
|
+
*/
|
|
12
|
+
export function moveCursorToStartOfLine() {
|
|
13
|
+
process.stdout.write(CARRIAGE_RETURN);
|
|
14
|
+
process.stdout.write(MOVE_RIGHT);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Move the cursor to the specified index (0-based)
|
|
19
|
+
* @param {number} current - Current index
|
|
20
|
+
* @param {number} target - Target index
|
|
21
|
+
*/
|
|
22
|
+
export function moveCursor(current, target) {
|
|
23
|
+
if (target < current) {
|
|
24
|
+
process.stdout.write(MOVE_UP(current - target));
|
|
25
|
+
} else if (target > current) {
|
|
26
|
+
process.stdout.write(MOVE_DOWN(target - current));
|
|
27
|
+
}
|
|
28
|
+
moveCursorToStartOfLine();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function askPermissionToContinue() {
|
|
32
|
+
const rl = createInterface({ input, output });
|
|
33
|
+
|
|
34
|
+
const answer = await rl.question('Do you want to continue? (y/n): ');
|
|
35
|
+
const userChoice = answer.toLowerCase() === 'y';
|
|
36
|
+
|
|
37
|
+
rl.close();
|
|
38
|
+
|
|
39
|
+
return userChoice;
|
|
40
|
+
}
|
package/src/lib/menu.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as readline from 'node:readline';
|
|
2
|
+
import { moveCursorToStartOfLine, moveCursor, MOVE_UP, CARRIAGE_RETURN } from './console_commons.js';
|
|
3
|
+
|
|
4
|
+
const EXPLANATORY_LINE_COUNT = 2;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Displays an interactive menu for selecting items from a list
|
|
8
|
+
* @param {string[]} items - List of items to display
|
|
9
|
+
* @returns {Promise<string[]>} Selected items, or empty array if user quits
|
|
10
|
+
*/
|
|
11
|
+
export async function interactiveSelect(items) {
|
|
12
|
+
if (!items || items.length === 0) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return new Promise((resolve, _reject) => {
|
|
17
|
+
const selectedLines = new Map();
|
|
18
|
+
let activeLine = 0;
|
|
19
|
+
|
|
20
|
+
// Display initial menu
|
|
21
|
+
for (const item of items) {
|
|
22
|
+
console.log(`[ ] ${item}`);
|
|
23
|
+
}
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log("Use Up/Down arrows to navigate, Space to toggle selection, 'y' to confirm, 'q' to quit.");
|
|
26
|
+
|
|
27
|
+
// Move cursor up to the first item
|
|
28
|
+
process.stdout.write(MOVE_UP(items.length + EXPLANATORY_LINE_COUNT));
|
|
29
|
+
moveCursorToStartOfLine();
|
|
30
|
+
|
|
31
|
+
// Set up raw mode for keypress detection
|
|
32
|
+
if (process.stdin.isTTY) {
|
|
33
|
+
process.stdin.setRawMode(true);
|
|
34
|
+
}
|
|
35
|
+
readline.emitKeypressEvents(process.stdin);
|
|
36
|
+
|
|
37
|
+
const onKeypress = (str, key) => {
|
|
38
|
+
// Handle Ctrl+C and Ctrl+D
|
|
39
|
+
if (key && key.ctrl && key.name === 'c') {
|
|
40
|
+
cleanup();
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (key && key.ctrl && key.name === 'd') {
|
|
45
|
+
cleanup();
|
|
46
|
+
resolve([]);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle arrow keys
|
|
51
|
+
if (key && key.name === 'up') {
|
|
52
|
+
if (activeLine > 0) {
|
|
53
|
+
moveCursor(activeLine, activeLine - 1);
|
|
54
|
+
activeLine--;
|
|
55
|
+
}
|
|
56
|
+
} else if (key && key.name === 'down') {
|
|
57
|
+
if (activeLine < items.length - 1) {
|
|
58
|
+
moveCursor(activeLine, activeLine + 1);
|
|
59
|
+
activeLine++;
|
|
60
|
+
}
|
|
61
|
+
} else if (str === ' ') {
|
|
62
|
+
// Toggle selection
|
|
63
|
+
if (selectedLines.has(activeLine)) {
|
|
64
|
+
selectedLines.delete(activeLine);
|
|
65
|
+
process.stdout.write(`${CARRIAGE_RETURN}[ ]`);
|
|
66
|
+
} else {
|
|
67
|
+
selectedLines.set(activeLine, true);
|
|
68
|
+
process.stdout.write(`${CARRIAGE_RETURN}[x]`);
|
|
69
|
+
}
|
|
70
|
+
moveCursorToStartOfLine();
|
|
71
|
+
} else if (str === 'y' || str === 'Y') {
|
|
72
|
+
// Confirm selection
|
|
73
|
+
cleanup();
|
|
74
|
+
|
|
75
|
+
// Move to one line below the end of the list
|
|
76
|
+
moveCursor(activeLine, items.length + EXPLANATORY_LINE_COUNT);
|
|
77
|
+
process.stdout.write(CARRIAGE_RETURN);
|
|
78
|
+
|
|
79
|
+
// Sort selected line numbers and get corresponding items
|
|
80
|
+
const selectedIndices = Array.from(selectedLines.keys()).sort((a, b) => a - b);
|
|
81
|
+
const selectedItems = selectedIndices.map((idx) => items[idx]);
|
|
82
|
+
resolve(selectedItems);
|
|
83
|
+
} else if (str === 'q' || str === 'Q' || (key && key.name === 'escape')) {
|
|
84
|
+
// Quit without selection
|
|
85
|
+
cleanup();
|
|
86
|
+
|
|
87
|
+
// Move to one line below the end of the list
|
|
88
|
+
moveCursor(activeLine, items.length + EXPLANATORY_LINE_COUNT);
|
|
89
|
+
process.stdout.write(CARRIAGE_RETURN);
|
|
90
|
+
|
|
91
|
+
resolve([]);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const cleanup = () => {
|
|
96
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
97
|
+
if (process.stdin.isTTY) {
|
|
98
|
+
process.stdin.setRawMode(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
process.stdin.on('keypress', onKeypress);
|
|
103
|
+
});
|
|
104
|
+
}
|
package/src/lib/os.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export function assertWindows() {
|
|
4
|
+
if (process.platform !== 'win32') {
|
|
5
|
+
throw new Error('This application can only be run on Windows.');
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Throws an error if winget is not available in PATH.
|
|
11
|
+
* @throws {Error} If winget is not found or not executable.
|
|
12
|
+
*/
|
|
13
|
+
export function assertWingetAvailable() {
|
|
14
|
+
const result = spawnSync('winget', ['--version'], { encoding: 'utf8' });
|
|
15
|
+
if (result.error || result.status !== 0) {
|
|
16
|
+
throw new Error('winget is not available. Please install winget and ensure it is in your PATH.');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves a list of package IDs that can be updated via winget
|
|
5
|
+
* @returns {string[]} Array of package IDs
|
|
6
|
+
* @throws {Error} If winget command fails or returns empty output
|
|
7
|
+
*/
|
|
8
|
+
export function getUpdateCandidateIds() {
|
|
9
|
+
const NAME_COL = 'Name';
|
|
10
|
+
const ID_COL = 'ID';
|
|
11
|
+
const VERSION_COL = 'Version';
|
|
12
|
+
const tableHeaderRegex = new RegExp(`.*${NAME_COL}\\s+${ID_COL}\\s+${VERSION_COL}.*`);
|
|
13
|
+
|
|
14
|
+
// Get winget upgrade list
|
|
15
|
+
const wgOutput = execWinget(['upgrade', '--include-unknown']);
|
|
16
|
+
|
|
17
|
+
if (!wgOutput || wgOutput.trim().length === 0) {
|
|
18
|
+
throw new Error('winget update command failed or returned empty output');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Remove last 2 lines (summary info + newline)
|
|
22
|
+
const lines = wgOutput
|
|
23
|
+
.split('\n')
|
|
24
|
+
.slice(0, -2)
|
|
25
|
+
.map((line) => line.trim());
|
|
26
|
+
|
|
27
|
+
const outputLineCount = lines.length;
|
|
28
|
+
const tableHeaderIndex = lines.findIndex((line) => line.match(tableHeaderRegex) !== null);
|
|
29
|
+
if (tableHeaderIndex === -1) {
|
|
30
|
+
throw new Error('Could not find table header in winget output');
|
|
31
|
+
}
|
|
32
|
+
// Find column positions
|
|
33
|
+
const namePos = lines[tableHeaderIndex].indexOf(NAME_COL);
|
|
34
|
+
const idPos = lines[tableHeaderIndex].indexOf(ID_COL);
|
|
35
|
+
const versionPos = lines[tableHeaderIndex].indexOf(VERSION_COL);
|
|
36
|
+
if (namePos === -1 || idPos === -1 || versionPos === -1) {
|
|
37
|
+
throw new Error('Could not find expected column headers in winget output');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Calculate column positions relative to start of each line
|
|
41
|
+
const packageIdStartPos = idPos - namePos;
|
|
42
|
+
const packageIdColLength = versionPos - idPos;
|
|
43
|
+
|
|
44
|
+
const packageListStartLineIndex = tableHeaderIndex + 2; // Skip header and separator line
|
|
45
|
+
const packageIds = [];
|
|
46
|
+
|
|
47
|
+
// Extract package IDs from each line
|
|
48
|
+
for (let i = packageListStartLineIndex; i < outputLineCount; i++) {
|
|
49
|
+
const line = lines[i];
|
|
50
|
+
if (!line || line.trim().length === 0) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const packageId = line.substring(packageIdStartPos, packageIdStartPos + packageIdColLength).trim();
|
|
55
|
+
|
|
56
|
+
if (packageId) {
|
|
57
|
+
packageIds.push(packageId);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return packageIds;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Runs winget update commands for the provided package IDs
|
|
66
|
+
* @param {string[]} ids - Package IDs to update
|
|
67
|
+
* @returns {Promise<void>}
|
|
68
|
+
*/
|
|
69
|
+
export async function runUpdates(ids, errorHandler = async (_id, _err) => false) {
|
|
70
|
+
for (const id of ids) {
|
|
71
|
+
console.log(`Updating package: ${id}`);
|
|
72
|
+
// prettier-ignore
|
|
73
|
+
try {
|
|
74
|
+
execWinget([
|
|
75
|
+
'upgrade',
|
|
76
|
+
'-i',
|
|
77
|
+
'--id',
|
|
78
|
+
id,
|
|
79
|
+
'--accept-source-agreements',
|
|
80
|
+
'--accept-package-agreements'
|
|
81
|
+
], { inheritStdio: true });
|
|
82
|
+
console.log(`Package ${id} updated successfully.`);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
// eslint-disable-next-line no-await-in-loop
|
|
85
|
+
if (!(await errorHandler(id, err))) {
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Synchronously executes winget command and returns stdout
|
|
94
|
+
* @param {string[]} args - Command arguments
|
|
95
|
+
* @param {Object} options - Execution options
|
|
96
|
+
* @param {boolean} options.inheritStdio - Whether to inherit stdio
|
|
97
|
+
* @returns {string} Command stdout
|
|
98
|
+
* @throws {Error} If command fails
|
|
99
|
+
*/
|
|
100
|
+
function execWinget(args, { inheritStdio = false } = {}) {
|
|
101
|
+
const stdio = inheritStdio ? 'inherit' : 'pipe';
|
|
102
|
+
const child = spawnSync('winget', args, { shell: false, stdio, encoding: 'utf8' });
|
|
103
|
+
|
|
104
|
+
const stdout = child.stdout || '';
|
|
105
|
+
const stderr = child.stderr || '';
|
|
106
|
+
|
|
107
|
+
if (child.error) {
|
|
108
|
+
throw new Error(`Failed to execute winget: ${child.error.message}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (child.status !== 0) {
|
|
112
|
+
throw new Error(`winget exited with code ${child.status}${stderr ? `: ${stderr}` : ''}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return stdout;
|
|
116
|
+
}
|