@homebridge-plugins/homebridge-august 3.2.2 → 3.3.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +200 -0
- package/.github/npm-version-script-esm.js +97 -0
- package/CHANGELOG.md +44 -9
- package/dist/Platform.Base.d.ts +288 -0
- package/dist/Platform.Base.d.ts.map +1 -0
- package/dist/Platform.Base.js +797 -0
- package/dist/Platform.Base.js.map +1 -0
- package/dist/Platform.HAP.d.ts +27 -0
- package/dist/Platform.HAP.d.ts.map +1 -0
- package/dist/Platform.HAP.js +68 -0
- package/dist/Platform.HAP.js.map +1 -0
- package/dist/connectivity-manager.d.ts +3 -3
- package/dist/connectivity-manager.d.ts.map +1 -1
- package/dist/connectivity-manager.js +57 -14
- package/dist/connectivity-manager.js.map +1 -1
- package/dist/devices/device.d.ts +5 -16
- package/dist/devices/device.d.ts.map +1 -1
- package/dist/devices/device.js +18 -75
- package/dist/devices/device.js.map +1 -1
- package/dist/devices/lock.d.ts +3 -3
- package/dist/devices/lock.d.ts.map +1 -1
- package/dist/devices/lock.js +41 -41
- package/dist/devices/lock.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/package.json +10 -8
- package/dist/platform.matter.d.ts +18 -20
- package/dist/platform.matter.d.ts.map +1 -1
- package/dist/platform.matter.js +74 -80
- package/dist/platform.matter.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +10 -8
- package/test/device.test.ts +384 -0
- package/test/{platform.test.ts → platform.hap.test.ts} +23 -23
- package/test/platform.polling.test.ts +5 -5
- package/.copilot/instructions.md +0 -261
- package/.github/scripts/compute-version-override.cjs +0 -51
- package/.github/scripts/normalize-changelog-header.cjs +0 -86
- package/.github/scripts/npm-version-script-esm-auto.js +0 -201
- package/.github/scripts/resolve-version-bump-esm.js +0 -118
- package/dist/platform.d.ts +0 -146
- package/dist/platform.d.ts.map +0 -1
- package/dist/platform.js +0 -755
- package/dist/platform.js.map +0 -1
- package/test/connectivity-manager.test.ts +0 -590
- package/test/platform.matter.test.ts +0 -660
package/.copilot/instructions.md
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
# Copilot Instructions for Homebridge August Plugin
|
|
2
|
-
|
|
3
|
-
## Repository Overview
|
|
4
|
-
|
|
5
|
-
This is a Homebridge plugin that provides HomeKit integration for August and Yale smart locks. The plugin is written in TypeScript with ES modules and follows Homebridge platform patterns.
|
|
6
|
-
|
|
7
|
-
**Branch Strategy**: This repository uses a beta-first development workflow where all changes must target beta branches before being merged to main. Version bumps are determined by issue labels (patch/minor/major) that must be set before assigning work to Copilot.
|
|
8
|
-
|
|
9
|
-
## Architecture
|
|
10
|
-
|
|
11
|
-
- **Plugin Type**: Homebridge dynamic platform plugin
|
|
12
|
-
- **Language**: TypeScript with ES modules (ES2022)
|
|
13
|
-
- **Runtime**: Node.js 20+ or 22+
|
|
14
|
-
- **Build System**: TypeScript compiler (tsc) with bundler module resolution
|
|
15
|
-
- **Testing**: Vitest with coverage support
|
|
16
|
-
- **Linting**: ESLint with Antfu config and custom rules
|
|
17
|
-
- **Documentation**: TypeDoc with modern theme
|
|
18
|
-
- **External API**: august-yale package for August/Yale API communication
|
|
19
|
-
|
|
20
|
-
## Key Files and Directories
|
|
21
|
-
|
|
22
|
-
- `src/` - TypeScript source code
|
|
23
|
-
- `index.ts` - Plugin entry point that registers the platform
|
|
24
|
-
- `platform.ts` - Main platform class (AugustPlatform)
|
|
25
|
-
- `settings.ts` - Configuration interfaces and constants
|
|
26
|
-
- `devices/` - Device-specific implementations
|
|
27
|
-
- `lock.ts` - Lock mechanism implementation
|
|
28
|
-
- `device.ts` - Base device class
|
|
29
|
-
- `homebridge-ui/` - Custom UI for Homebridge Config UI X
|
|
30
|
-
- `*.test.ts` - Test files (Vitest)
|
|
31
|
-
- `dist/` - Compiled JavaScript output (generated, excluded from git)
|
|
32
|
-
- `config.schema.json` - Homebridge configuration schema with custom UI
|
|
33
|
-
- `package.json` - Dependencies and npm scripts
|
|
34
|
-
- `tsconfig.json` - TypeScript configuration (ES2022, bundler resolution)
|
|
35
|
-
- `eslint.config.js` - ESLint configuration with Antfu base and custom rules
|
|
36
|
-
|
|
37
|
-
## Development Workflow
|
|
38
|
-
|
|
39
|
-
### Branch Strategy and PR Workflow
|
|
40
|
-
|
|
41
|
-
**IMPORTANT**: All pull requests must target a beta branch first, never directly to the main branch.
|
|
42
|
-
|
|
43
|
-
#### Beta Branch Requirements
|
|
44
|
-
- All PRs must be directed to a branch that starts with "beta-"
|
|
45
|
-
- Beta branches should be named `beta-X.Y.Z` based on the expected version bump
|
|
46
|
-
- If no appropriate beta branch exists, create one based on the next possible version:
|
|
47
|
-
- **patch** releases (bug fixes): `beta-X.Y.Z+1` (e.g., current 3.0.2 → beta-3.0.3)
|
|
48
|
-
- **minor** releases (new features): `beta-X.Y+1.0` (e.g., current 3.0.2 → beta-3.1.0)
|
|
49
|
-
- **major** releases (breaking changes): `beta-X+1.0.0` (e.g., current 3.0.2 → beta-4.0.0)
|
|
50
|
-
|
|
51
|
-
#### Required Labels
|
|
52
|
-
Before assigning any issue to Copilot, the following labels **must** be set to determine the version bump:
|
|
53
|
-
- `patch` - for bug fixes and minor improvements
|
|
54
|
-
- `minor` - for new features and enhancements
|
|
55
|
-
- `major` - for breaking changes
|
|
56
|
-
|
|
57
|
-
#### Workflow Steps
|
|
58
|
-
1. Ensure proper label (patch/minor/major) is set on the issue
|
|
59
|
-
2. Identify or create appropriate beta branch based on the label
|
|
60
|
-
3. Target all development work and PRs to the beta branch
|
|
61
|
-
4. Once beta testing is complete, merge beta branch to main for release
|
|
62
|
-
|
|
63
|
-
### Building
|
|
64
|
-
```bash
|
|
65
|
-
npm run build # Full build: clean + compile + copy UI files
|
|
66
|
-
npm run clean # Remove dist directory
|
|
67
|
-
npm run watch # Build + link + watch with nodemon
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Testing
|
|
71
|
-
```bash
|
|
72
|
-
npm test # Run Vitest tests once
|
|
73
|
-
npm run test:watch # Run tests in watch mode
|
|
74
|
-
npm run test-coverage # Run tests with coverage report
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Linting
|
|
78
|
-
```bash
|
|
79
|
-
npm run lint # Check for ESLint issues
|
|
80
|
-
npm run lint:fix # Auto-fix ESLint issues
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Documentation
|
|
84
|
-
```bash
|
|
85
|
-
npm run docs # Generate TypeDoc documentation
|
|
86
|
-
npm run docs:lint # Validate documentation (treat warnings as errors)
|
|
87
|
-
npm run docs:theme # Generate docs with default-modern theme
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Development Setup
|
|
91
|
-
```bash
|
|
92
|
-
npm run watch # Start development with hot reload
|
|
93
|
-
npm run plugin-ui # Copy UI files to dist
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Coding Standards
|
|
97
|
-
|
|
98
|
-
1. **TypeScript**:
|
|
99
|
-
- ES2022 target with bundler module resolution
|
|
100
|
-
- Strict mode enabled (except noImplicitAny: false)
|
|
101
|
-
- Use ES modules with `.js` file extensions in imports
|
|
102
|
-
- Generate declaration files and source maps
|
|
103
|
-
|
|
104
|
-
2. **ESLint**:
|
|
105
|
-
- Antfu ESLint config as base
|
|
106
|
-
- Custom rules for import ordering (perfectionist)
|
|
107
|
-
- JSDoc alignment enforcement
|
|
108
|
-
- Multi-line curly braces only
|
|
109
|
-
- Consistent quote props
|
|
110
|
-
- Test-specific rules (no-only-tests)
|
|
111
|
-
|
|
112
|
-
3. **Code Style**:
|
|
113
|
-
- Use 1TBS brace style with single line allowed
|
|
114
|
-
- Sort imports by type (builtin, external, internal, relative)
|
|
115
|
-
- Sort named imports and exports
|
|
116
|
-
- Use proper JSDoc formatting and alignment
|
|
117
|
-
|
|
118
|
-
4. **Homebridge Patterns**:
|
|
119
|
-
- Implement DynamicPlatformPlugin interface
|
|
120
|
-
- Use proper platform registration in index.ts
|
|
121
|
-
- Follow accessory lifecycle management
|
|
122
|
-
- Implement proper logging with Homebridge logger
|
|
123
|
-
- Use PlatformAccessory for device management
|
|
124
|
-
|
|
125
|
-
5. **Error Handling**:
|
|
126
|
-
- Proper try-catch blocks for async operations
|
|
127
|
-
- Meaningful error messages with context
|
|
128
|
-
- Graceful degradation for API failures
|
|
129
|
-
|
|
130
|
-
## Homebridge Specifics
|
|
131
|
-
|
|
132
|
-
- **Platform Type**: Dynamic platform plugin (not accessory plugin)
|
|
133
|
-
- **Platform Name**: "August" (defined in settings.ts)
|
|
134
|
-
- **Plugin Name**: "homebridge-august" (must match package.json name)
|
|
135
|
-
- **Configuration Schema**: Uses config.schema.json with custom UI
|
|
136
|
-
- **Custom UI**: Located in src/homebridge-ui/ with custom configuration interface
|
|
137
|
-
- **Platform Class**: AugustPlatform extends DynamicPlatformPlugin
|
|
138
|
-
- **Device Management**: Uses PlatformAccessory for caching and state management
|
|
139
|
-
- **HAP Services**: Implement LockMechanism service for smart locks
|
|
140
|
-
- **Authentication**: Handle August API authentication with validation codes
|
|
141
|
-
- **Rate Limiting**: Implement proper refresh/update/push rates for API calls
|
|
142
|
-
|
|
143
|
-
## August/Yale Integration
|
|
144
|
-
|
|
145
|
-
- **API Package**: Uses `august-yale` npm package for API communication
|
|
146
|
-
- **Authentication Flow**:
|
|
147
|
-
1. User provides augustId (email/phone) and password
|
|
148
|
-
2. System sends validation code to user
|
|
149
|
-
3. User enters validation code to complete authentication
|
|
150
|
-
4. API credentials are stored and used for subsequent requests
|
|
151
|
-
- **Device Types**: Support multiple lock types (August Smart Lock, Yale Assure Lock variants)
|
|
152
|
-
- **State Synchronization**: Keep HomeKit state in sync with August/Yale API
|
|
153
|
-
- **Polling**: Implement configurable refresh rates for device state updates
|
|
154
|
-
- **Configuration**: Use credentials interface for API authentication settings
|
|
155
|
-
|
|
156
|
-
## When Making Changes
|
|
157
|
-
|
|
158
|
-
**CRITICAL**: Before starting any work, ensure the issue has the appropriate label (patch/minor/major) and target the correct beta branch.
|
|
159
|
-
|
|
160
|
-
0. **Branch Targeting**:
|
|
161
|
-
- Never target PRs directly to main branch
|
|
162
|
-
- Always target a beta branch (beta-X.Y.Z format)
|
|
163
|
-
- Create beta branch if none exists for the expected version
|
|
164
|
-
- Use issue labels to determine version bump:
|
|
165
|
-
- `patch`: Bug fixes → beta-X.Y.Z+1
|
|
166
|
-
- `minor`: New features → beta-X.Y+1.0
|
|
167
|
-
- `major`: Breaking changes → beta-X+1.0.0
|
|
168
|
-
|
|
169
|
-
1. **Dependencies**:
|
|
170
|
-
- Only add well-maintained dependencies
|
|
171
|
-
- Keep runtime dependencies minimal
|
|
172
|
-
- Update package.json and package-lock.json properly
|
|
173
|
-
- Consider bundle size impact
|
|
174
|
-
|
|
175
|
-
2. **Breaking Changes**:
|
|
176
|
-
- Follow semantic versioning
|
|
177
|
-
- Update CHANGELOG.md appropriately
|
|
178
|
-
- Consider migration paths for users
|
|
179
|
-
- Test with multiple Homebridge versions
|
|
180
|
-
- **Must** use `major` label and target appropriate beta-X+1.0.0 branch
|
|
181
|
-
|
|
182
|
-
3. **Configuration**:
|
|
183
|
-
- Update config.schema.json for new options
|
|
184
|
-
- Maintain backward compatibility
|
|
185
|
-
- Add proper validation and defaults
|
|
186
|
-
- Update TypeScript interfaces in settings.ts
|
|
187
|
-
|
|
188
|
-
4. **Documentation**:
|
|
189
|
-
- Update README.md for user-facing changes
|
|
190
|
-
- Add TSDoc comments for new APIs
|
|
191
|
-
- Update TypeDoc comments for generated docs
|
|
192
|
-
- Consider updating examples
|
|
193
|
-
|
|
194
|
-
5. **Testing**:
|
|
195
|
-
- Add Vitest tests for new features
|
|
196
|
-
- Mock external dependencies (August API)
|
|
197
|
-
- Test error scenarios and edge cases
|
|
198
|
-
- Maintain test coverage
|
|
199
|
-
|
|
200
|
-
6. **Compatibility**:
|
|
201
|
-
- Support Node.js 20+ and 22+
|
|
202
|
-
- Support Homebridge 1.9.0+ and 2.0.0+
|
|
203
|
-
- Test with Config UI X integration
|
|
204
|
-
- Verify ES module compatibility
|
|
205
|
-
|
|
206
|
-
## Common Patterns
|
|
207
|
-
|
|
208
|
-
- **RxJS**: Use reactive programming patterns where appropriate
|
|
209
|
-
- **Async/Await**: Prefer async/await over Promise chains
|
|
210
|
-
- **ES Modules**: Use ES module syntax with `.js` extensions in imports
|
|
211
|
-
- **Interface Design**: Define clear TypeScript interfaces in settings.ts
|
|
212
|
-
- **Error Handling**: Implement comprehensive error handling with meaningful messages
|
|
213
|
-
- **Logging**: Use this.log with appropriate log levels (info, warn, error, debug)
|
|
214
|
-
- **Device Lifecycle**: Implement proper setup/cleanup in platform methods
|
|
215
|
-
- **Configuration Validation**: Validate user configuration and provide helpful error messages
|
|
216
|
-
- **API Rate Limiting**: Respect August API rate limits with configurable intervals
|
|
217
|
-
- **State Management**: Maintain device state consistency between API and HomeKit
|
|
218
|
-
|
|
219
|
-
## Important Constants and Settings
|
|
220
|
-
|
|
221
|
-
- **PLATFORM_NAME**: "August" (used in Homebridge registration)
|
|
222
|
-
- **PLUGIN_NAME**: "homebridge-august" (must match package.json)
|
|
223
|
-
- **Plugin Type**: "platform" (not accessory)
|
|
224
|
-
- **Custom UI**: Enabled with path "./dist/homebridge-ui"
|
|
225
|
-
- **Configuration**: Uses credentials and options interfaces
|
|
226
|
-
- **Device Types**: Support for various August and Yale lock models
|
|
227
|
-
|
|
228
|
-
## Testing Guidelines
|
|
229
|
-
|
|
230
|
-
- **Framework**: Use Vitest for testing with TypeScript support
|
|
231
|
-
- **Test Files**: Name test files with `.test.ts` extension in src/
|
|
232
|
-
- **Coverage**: Aim for good test coverage, use `npm run test-coverage`
|
|
233
|
-
- **Mocking**: Mock external dependencies, especially:
|
|
234
|
-
- August/Yale API calls
|
|
235
|
-
- Homebridge API interactions
|
|
236
|
-
- File system operations
|
|
237
|
-
- **Test Categories**:
|
|
238
|
-
- Unit tests for individual functions and classes
|
|
239
|
-
- Integration tests for platform initialization
|
|
240
|
-
- Configuration validation tests
|
|
241
|
-
- Error handling tests
|
|
242
|
-
- **Test Data**: Use realistic test data that matches August API responses
|
|
243
|
-
- **Async Testing**: Properly test async operations and promises
|
|
244
|
-
- **CI/CD**: Ensure tests run in continuous integration pipeline
|
|
245
|
-
|
|
246
|
-
## Files to Avoid Modifying
|
|
247
|
-
|
|
248
|
-
- `dist/` directory (generated by TypeScript compilation)
|
|
249
|
-
- `node_modules/` directory (managed by npm)
|
|
250
|
-
- `docs/` directory (generated by TypeDoc)
|
|
251
|
-
- `.git/` directory and git metadata
|
|
252
|
-
- Files listed in `.gitignore`
|
|
253
|
-
- `package-lock.json` (only modify through npm commands)
|
|
254
|
-
|
|
255
|
-
## Security Considerations
|
|
256
|
-
|
|
257
|
-
- **Credentials**: Never log or expose user credentials
|
|
258
|
-
- **API Keys**: Handle August API credentials securely
|
|
259
|
-
- **Validation**: Validate all user inputs
|
|
260
|
-
- **Error Messages**: Don't expose sensitive information in error messages
|
|
261
|
-
- **Storage**: Store credentials securely using Homebridge storage mechanisms
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const child = require('node:child_process')
|
|
4
|
-
const fs = require('node:fs')
|
|
5
|
-
|
|
6
|
-
const bump = (process.argv[2] || 'patch').toLowerCase()
|
|
7
|
-
const allowed = new Set(['major', 'minor', 'patch'])
|
|
8
|
-
|
|
9
|
-
if (!allowed.has(bump)) {
|
|
10
|
-
console.error(`Invalid bump level '${bump}'. Use major, minor, or patch.`)
|
|
11
|
-
process.exit(1)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
|
15
|
-
|
|
16
|
-
function inc(v, level) {
|
|
17
|
-
const parts = v.split('-')[0].split('+')[0].split('.').map(Number)
|
|
18
|
-
if (level === 'major') {
|
|
19
|
-
parts[0] += 1
|
|
20
|
-
parts[1] = 0
|
|
21
|
-
parts[2] = 0
|
|
22
|
-
} else if (level === 'minor') {
|
|
23
|
-
parts[1] += 1
|
|
24
|
-
parts[2] = 0
|
|
25
|
-
} else {
|
|
26
|
-
parts[2] += 1
|
|
27
|
-
}
|
|
28
|
-
return parts.join('.')
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function latestStable(name) {
|
|
32
|
-
try {
|
|
33
|
-
const versions = JSON.parse(child.execSync(`npm info ${name} versions --json`).toString('utf8').trim())
|
|
34
|
-
const stable = (Array.isArray(versions) ? versions : [])
|
|
35
|
-
.filter(v => /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/.test(v) && !v.includes('-'))
|
|
36
|
-
stable.sort((a, b) => {
|
|
37
|
-
const ap = a.split('.').map(Number)
|
|
38
|
-
const bp = b.split('.').map(Number)
|
|
39
|
-
for (let i = 0; i < 3; i++) {
|
|
40
|
-
if (ap[i] !== bp[i]) return bp[i] - ap[i]
|
|
41
|
-
}
|
|
42
|
-
return 0
|
|
43
|
-
})
|
|
44
|
-
return stable[0] || ''
|
|
45
|
-
} catch {
|
|
46
|
-
return ''
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const base = latestStable(pkg.name) || pkg.version
|
|
51
|
-
process.stdout.write(inc(base, bump))
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const child_process = require('node:child_process')
|
|
4
|
-
const fs = require('node:fs')
|
|
5
|
-
|
|
6
|
-
const changelogPath = 'CHANGELOG.md'
|
|
7
|
-
|
|
8
|
-
function normalizeChangelogHeader(content) {
|
|
9
|
-
const headerStart = content.indexOf('# Changelog\n')
|
|
10
|
-
if (headerStart <= 0) {
|
|
11
|
-
return content
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const nextSectionStart = content.indexOf('\n## ', headerStart + 1)
|
|
15
|
-
const headerBlock = (nextSectionStart === -1 ? content.slice(headerStart) : content.slice(headerStart, nextSectionStart)).trim()
|
|
16
|
-
const prependedEntries = content.slice(0, headerStart).trim()
|
|
17
|
-
const remainingEntries = (nextSectionStart === -1 ? '' : content.slice(nextSectionStart)).trim()
|
|
18
|
-
|
|
19
|
-
return [headerBlock, prependedEntries, remainingEntries].filter(Boolean).join('\n\n') + '\n'
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getRepoUrl() {
|
|
23
|
-
try {
|
|
24
|
-
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
|
25
|
-
let url = (typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url) || ''
|
|
26
|
-
url = url.replace(/^git\+/, '').replace(/\.git$/, '')
|
|
27
|
-
if (url.startsWith('https://')) return url
|
|
28
|
-
} catch {}
|
|
29
|
-
try {
|
|
30
|
-
const remote = child_process.execSync('git remote get-url origin').toString().trim()
|
|
31
|
-
return remote.replace(/^git\+/, '').replace(/\.git$/, '').replace(/^git@github\.com:/, 'https://github.com/')
|
|
32
|
-
} catch {}
|
|
33
|
-
return ''
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function getPreviousTag() {
|
|
37
|
-
try {
|
|
38
|
-
const tags = child_process
|
|
39
|
-
.execSync('git tag --sort=-version:refname')
|
|
40
|
-
.toString()
|
|
41
|
-
.trim()
|
|
42
|
-
.split('\n')
|
|
43
|
-
.filter(Boolean)
|
|
44
|
-
return tags[0] || ''
|
|
45
|
-
} catch {
|
|
46
|
-
return ''
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function appendFullChangelogLink(content, currentTag, repoUrl) {
|
|
51
|
-
const prefix = `**Full Changelog**: ${repoUrl}/compare/`
|
|
52
|
-
// Find the bounds of the first release section (the newly generated one)
|
|
53
|
-
const firstSectionStart = content.indexOf('\n## ')
|
|
54
|
-
if (firstSectionStart === -1) return content
|
|
55
|
-
const firstSectionEnd = content.indexOf('\n## ', firstSectionStart + 1)
|
|
56
|
-
const firstSection = firstSectionEnd === -1 ? content.slice(firstSectionStart) : content.slice(firstSectionStart, firstSectionEnd)
|
|
57
|
-
|
|
58
|
-
// Skip if a Full Changelog link is already present in that section
|
|
59
|
-
if (firstSection.includes(prefix)) return content
|
|
60
|
-
|
|
61
|
-
const prevTag = getPreviousTag()
|
|
62
|
-
if (!prevTag) return content
|
|
63
|
-
|
|
64
|
-
const link = `**Full Changelog**: ${repoUrl}/compare/${prevTag}...${currentTag}`
|
|
65
|
-
if (firstSectionEnd === -1) {
|
|
66
|
-
return content.trimEnd() + '\n\n' + link + '\n'
|
|
67
|
-
}
|
|
68
|
-
return content.slice(0, firstSectionEnd).trimEnd() + '\n\n' + link + '\n' + content.slice(firstSectionEnd)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
exports.preCommit = (props) => {
|
|
72
|
-
if (!fs.existsSync(changelogPath)) {
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let content = fs.readFileSync(changelogPath, 'utf8')
|
|
77
|
-
content = normalizeChangelogHeader(content)
|
|
78
|
-
|
|
79
|
-
const repoUrl = getRepoUrl()
|
|
80
|
-
const currentTag = props?.tag || ''
|
|
81
|
-
if (repoUrl && currentTag) {
|
|
82
|
-
content = appendFullChangelogLink(content, currentTag, repoUrl)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
fs.writeFileSync(changelogPath, content)
|
|
86
|
-
}
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Standalone ESM implementation — self-contained so this file can be copied
|
|
5
|
-
* into other repos without pulling any other files.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import assert from 'node:assert'
|
|
9
|
-
import child_process from 'node:child_process'
|
|
10
|
-
import fs from 'node:fs'
|
|
11
|
-
import process from 'node:process'
|
|
12
|
-
// Minimal, in-file semver utilities used by this script.
|
|
13
|
-
// This avoids an external dependency so the script can run in environments
|
|
14
|
-
// where `semver` isn't installed.
|
|
15
|
-
const semver = {
|
|
16
|
-
// Very small validation: match major.minor.patch with optional prerelease/build
|
|
17
|
-
valid(v) {
|
|
18
|
-
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/.test(v)
|
|
19
|
-
},
|
|
20
|
-
// Return prerelease identifiers array or null
|
|
21
|
-
prerelease(v) {
|
|
22
|
-
const m = v.match(/^\d+\.\d+\.\d+-(.+)$/)
|
|
23
|
-
return m ? m[1].split('.') : null
|
|
24
|
-
},
|
|
25
|
-
// Compare two semver strings in reverse order (for rcompare use)
|
|
26
|
-
rcompare(a, b) {
|
|
27
|
-
// Use standard semver precedence rules for major.minor.patch; ignore prerelease complexity except that prerelease is lower than release
|
|
28
|
-
const pa = a.split('-')
|
|
29
|
-
const pb = b.split('-')
|
|
30
|
-
const aCore = pa[0].split('.').map(Number)
|
|
31
|
-
const bCore = pb[0].split('.').map(Number)
|
|
32
|
-
for (let i = 0; i < 3; i++) {
|
|
33
|
-
if (aCore[i] > bCore[i]) return -1
|
|
34
|
-
if (aCore[i] < bCore[i]) return 1
|
|
35
|
-
}
|
|
36
|
-
// equal core; releases (no prerelease) are greater than prerelease
|
|
37
|
-
if (pa.length === 1 && pb.length > 1) return -1
|
|
38
|
-
if (pa.length > 1 && pb.length === 1) return 1
|
|
39
|
-
// both same prerelease status: compare as strings
|
|
40
|
-
if (a > b) return -1
|
|
41
|
-
if (a < b) return 1
|
|
42
|
-
return 0
|
|
43
|
-
},
|
|
44
|
-
// Increment version by 'major'|'minor'|'patch'
|
|
45
|
-
inc(v, level) {
|
|
46
|
-
if (!this.valid(v)) throw new Error(`Invalid semver passed to inc: ${v}`)
|
|
47
|
-
// strip prerelease/build
|
|
48
|
-
const core = v.split('-')[0].split('+')[0]
|
|
49
|
-
const parts = core.split('.').map(Number)
|
|
50
|
-
switch (level) {
|
|
51
|
-
case 'major':
|
|
52
|
-
parts[0] += 1
|
|
53
|
-
parts[1] = 0
|
|
54
|
-
parts[2] = 0
|
|
55
|
-
break
|
|
56
|
-
case 'minor':
|
|
57
|
-
parts[1] += 1
|
|
58
|
-
parts[2] = 0
|
|
59
|
-
break
|
|
60
|
-
case 'patch':
|
|
61
|
-
default:
|
|
62
|
-
parts[2] += 1
|
|
63
|
-
break
|
|
64
|
-
}
|
|
65
|
-
return parts.join('.')
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const BRANCH_VERSION_PATTERN = /^([A-Z]+)-(\d+\.\d+\.\d+)$/i
|
|
70
|
-
|
|
71
|
-
// Load package.json
|
|
72
|
-
const packageJSON = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
|
73
|
-
|
|
74
|
-
const refArgument = process.argv[2]
|
|
75
|
-
const tagArgument = process.argv[3] || 'latest'
|
|
76
|
-
|
|
77
|
-
if (!refArgument) {
|
|
78
|
-
console.error('ref argument is missing')
|
|
79
|
-
console.error('Usage: npm-version-script-esm.js <ref> [tag]')
|
|
80
|
-
process.exit(1)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Queries the NPM registry for the latest version for the provided base version and tag.
|
|
85
|
-
* If the tag is latest, then the base version is returned if it exists. For other tags, the latest
|
|
86
|
-
* version found for that base version and tag is returned.
|
|
87
|
-
* @param baseVersion The base version to query for, e.g. 2.0.0
|
|
88
|
-
* @param tag The tag to query for, e.g. beta or latest
|
|
89
|
-
* @returns {string} Returns the version, or '' if not found
|
|
90
|
-
*/
|
|
91
|
-
function getTagVersionFromNpm(baseVersion, tag) {
|
|
92
|
-
try {
|
|
93
|
-
return JSON.parse(child_process.execSync(`npm info ${packageJSON.name} versions --json`).toString('utf8').trim())
|
|
94
|
-
.filter(v => (tag === 'latest' ? v === baseVersion : v.startsWith(`${baseVersion}-${tag}.`)))
|
|
95
|
-
.reduce((_, current) => current, '')
|
|
96
|
-
} catch (e) {
|
|
97
|
-
console.error(`Failed to query the npm registry for the latest version for tag: ${tag}`, e)
|
|
98
|
-
// throw e;
|
|
99
|
-
return ''
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getLatestStableVersionFromNpm() {
|
|
104
|
-
try {
|
|
105
|
-
const versions = JSON.parse(child_process.execSync(`npm info ${packageJSON.name} versions --json`).toString('utf8').trim())
|
|
106
|
-
if (!Array.isArray(versions) || versions.length === 0) return ''
|
|
107
|
-
|
|
108
|
-
// Only consider stable semver versions (no prerelease)
|
|
109
|
-
const stable = versions.filter(v => semver.valid(v) && !semver.prerelease(v))
|
|
110
|
-
if (stable.length === 0) return ''
|
|
111
|
-
|
|
112
|
-
// Sort descending and return the highest
|
|
113
|
-
const sorted = stable.sort(semver.rcompare)
|
|
114
|
-
return sorted[0]
|
|
115
|
-
} catch (e) {
|
|
116
|
-
console.error('Failed to query the npm registry for published versions', e)
|
|
117
|
-
return ''
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function desiredTargetVersion(ref) {
|
|
122
|
-
// ref is a GitHub action ref string
|
|
123
|
-
if (ref.startsWith('refs/pull/')) {
|
|
124
|
-
throw new Error('The version script was executed inside a PR!')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
assert(ref.startsWith('refs/heads/'))
|
|
128
|
-
const branchName = ref.slice('refs/heads/'.length)
|
|
129
|
-
|
|
130
|
-
const results = branchName.match(BRANCH_VERSION_PATTERN)
|
|
131
|
-
if (results !== null) {
|
|
132
|
-
if (results[1].toLowerCase() !== tagArgument.toLowerCase()) {
|
|
133
|
-
console.warn(`The base branch name (${results[1]}) differs from the tag name ${tagArgument}`)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return results[2]
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (branchName === 'latest') {
|
|
140
|
-
// For latest branch, start with package.json version as base
|
|
141
|
-
return packageJSON.version
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
throw new Error(`Malformed branch name for ref: ${ref}. Must be beta-x.x.x, alpha-x.x.x, or 'latest'`)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function bumpVersion(baseVersion, level) {
|
|
148
|
-
const normalizedLevel = (level || 'patch').toLowerCase()
|
|
149
|
-
if (!semver.valid(baseVersion)) {
|
|
150
|
-
throw new Error(`Invalid base version passed to bumpVersion: ${baseVersion}`)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
switch (normalizedLevel) {
|
|
154
|
-
case 'major':
|
|
155
|
-
return semver.inc(baseVersion, 'major')
|
|
156
|
-
case 'minor':
|
|
157
|
-
return semver.inc(baseVersion, 'minor')
|
|
158
|
-
case 'patch':
|
|
159
|
-
default:
|
|
160
|
-
return semver.inc(baseVersion, 'patch')
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const baseVersion = desiredTargetVersion(refArgument)
|
|
165
|
-
let publishTag = baseVersion
|
|
166
|
-
|
|
167
|
-
if (refArgument.includes('latest')) {
|
|
168
|
-
// For latest branch, determine next version based on npm and bump patch.
|
|
169
|
-
const latestPublishedStable = getLatestStableVersionFromNpm()
|
|
170
|
-
if (latestPublishedStable) {
|
|
171
|
-
console.warn(`Latest published stable version is ${latestPublishedStable}; bumping patch`)
|
|
172
|
-
publishTag = bumpVersion(latestPublishedStable, 'patch')
|
|
173
|
-
} else {
|
|
174
|
-
console.warn('No published stable versions found; bumping package.json version patch')
|
|
175
|
-
publishTag = bumpVersion(baseVersion, 'patch')
|
|
176
|
-
}
|
|
177
|
-
} else {
|
|
178
|
-
// For alpha/beta, query npm for latest
|
|
179
|
-
const latestReleasedVersion = getTagVersionFromNpm(baseVersion, tagArgument)
|
|
180
|
-
if (latestReleasedVersion) {
|
|
181
|
-
console.warn(`Latest published version for ${baseVersion} with tag ${tagArgument} is ${latestReleasedVersion}`)
|
|
182
|
-
publishTag = latestReleasedVersion
|
|
183
|
-
} else {
|
|
184
|
-
console.warn(`No published versions for ${baseVersion} with tag ${tagArgument}`)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (packageJSON.version !== publishTag) {
|
|
189
|
-
console.warn(`Updating version in package.json from ${packageJSON.version} to ${publishTag}`)
|
|
190
|
-
packageJSON.version = publishTag
|
|
191
|
-
fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2))
|
|
192
|
-
|
|
193
|
-
// perform the same change to the package-lock.json
|
|
194
|
-
const packageLockJSON = JSON.parse(fs.readFileSync('package-lock.json', 'utf8'))
|
|
195
|
-
packageLockJSON.version = publishTag
|
|
196
|
-
fs.writeFileSync('package-lock.json', JSON.stringify(packageLockJSON, null, 2))
|
|
197
|
-
} else {
|
|
198
|
-
console.warn(`Version in package.json is already ${packageJSON.version}`)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
console.log(publishTag)
|