nim-sync 1.0.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/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/file-utils.d.ts +104 -0
- package/dist/lib/file-utils.d.ts.map +1 -0
- package/dist/lib/file-utils.js +312 -0
- package/dist/lib/file-utils.js.map +1 -0
- package/dist/lib/retry.d.ts +35 -0
- package/dist/lib/retry.d.ts.map +1 -0
- package/dist/lib/retry.js +122 -0
- package/dist/lib/retry.js.map +1 -0
- package/dist/nim-sync.mjs +1989 -0
- package/dist/nim-sync.mjs.map +7 -0
- package/dist/plugin/nim-sync.d.ts +46 -0
- package/dist/plugin/nim-sync.d.ts.map +1 -0
- package/dist/plugin/nim-sync.js +366 -0
- package/dist/plugin/nim-sync.js.map +1 -0
- package/dist/types/index.d.ts +103 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +14 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/schema.d.ts +92 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +135 -0
- package/dist/types/schema.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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,199 @@
|
|
|
1
|
+
# OpenCode NVIDIA NIM Sync Plugin
|
|
2
|
+
|
|
3
|
+
A global OpenCode plugin that automatically synchronizes NVIDIA NIM models with your OpenCode configuration on startup.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Sync**: On OpenCode startup, fetches the latest NVIDIA model catalog
|
|
8
|
+
- **Config Management**: Updates `provider.nim.models` in your OpenCode config
|
|
9
|
+
- **TTL Cache**: Only refreshes models if last refresh was >24 hours ago
|
|
10
|
+
- **Manual Refresh**: `/nim-refresh` command for force updates
|
|
11
|
+
- **Atomic Operations**: Safe file writes with backups and locking
|
|
12
|
+
- **Error Handling**: Graceful fallback when offline or missing API key
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Install the published plugin from npm through your OpenCode config.
|
|
17
|
+
npm plugins are installed automatically using Bun at startup, so end users do not need to run `npm install`, build the plugin locally, or copy files into the plugins directory.
|
|
18
|
+
If you are working from this repository before the package is published, use the local testing flow farther below instead.
|
|
19
|
+
|
|
20
|
+
Add `nim-sync` to the `plugin` array in your global or project OpenCode config:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"$schema": "https://opencode.ai/config.json",
|
|
25
|
+
"plugin": ["nim-sync"]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
After adding the plugin, restart OpenCode.
|
|
30
|
+
Ensure you have an NVIDIA API key either:
|
|
31
|
+
- Set `NVIDIA_API_KEY` environment variable
|
|
32
|
+
- Run `/connect` in OpenCode to add NVIDIA credentials
|
|
33
|
+
|
|
34
|
+
On startup, the plugin refreshes the NVIDIA model catalog in the background and registers `/nim-refresh` for manual updates.
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
The plugin manages this subtree in your OpenCode config:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"provider": {
|
|
43
|
+
"nim": {
|
|
44
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
45
|
+
"name": "NVIDIA NIM",
|
|
46
|
+
"options": {
|
|
47
|
+
"baseURL": "https://integrate.api.nvidia.com/v1"
|
|
48
|
+
},
|
|
49
|
+
"models": {
|
|
50
|
+
"meta/llama-3.1-70b-instruct": {
|
|
51
|
+
"name": "Meta Llama 3.1 70B Instruct"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## User Ownership
|
|
60
|
+
|
|
61
|
+
The plugin ONLY manages:
|
|
62
|
+
- `provider.nim.npm`
|
|
63
|
+
- `provider.nim.name`
|
|
64
|
+
- `provider.nim.options.baseURL`
|
|
65
|
+
- `provider.nim.models`
|
|
66
|
+
|
|
67
|
+
You retain control over:
|
|
68
|
+
- Top-level `model` selection
|
|
69
|
+
- `small_model` setting
|
|
70
|
+
- Per-model option overrides
|
|
71
|
+
- Any unrelated providers
|
|
72
|
+
|
|
73
|
+
## Development
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Install dependencies
|
|
77
|
+
npm install
|
|
78
|
+
|
|
79
|
+
# Run tests
|
|
80
|
+
npm test -- --run
|
|
81
|
+
|
|
82
|
+
# Run tests with coverage
|
|
83
|
+
npm run test:coverage -- --run
|
|
84
|
+
|
|
85
|
+
# Build the bundled plugin artifact
|
|
86
|
+
npm run build
|
|
87
|
+
|
|
88
|
+
# Type check
|
|
89
|
+
npm run typecheck
|
|
90
|
+
|
|
91
|
+
# Lint
|
|
92
|
+
npm run lint
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Local Testing
|
|
96
|
+
|
|
97
|
+
If you are testing the plugin before it is published to npm, you can still use the bundled artifact in `dist/nim-sync.mjs` with OpenCode's local plugin directory.
|
|
98
|
+
|
|
99
|
+
## Release Automation
|
|
100
|
+
|
|
101
|
+
This repository includes [`.github/workflows/publish.yml`](.github/workflows/publish.yml), which publishes to npm whenever you push a `v*` tag.
|
|
102
|
+
The workflow verifies that the tag matches `package.json`, then runs `npm ci`, tests, lint, typecheck, build, and `npm publish`.
|
|
103
|
+
|
|
104
|
+
### One-Time Setup
|
|
105
|
+
|
|
106
|
+
Because `nim-sync` is not published yet, the first release must be done manually once so the package exists on npm.
|
|
107
|
+
|
|
108
|
+
1. Log into npm on your release machine:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm adduser
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
2. Publish the first version from this repository root:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npm publish
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
3. Open the npm package settings for `nim-sync` and add a `Trusted Publisher` for GitHub Actions:
|
|
121
|
+
- Owner/user: `EthanBerlant`
|
|
122
|
+
- Repository: `nim-sync`
|
|
123
|
+
- Workflow filename: `publish.yml`
|
|
124
|
+
|
|
125
|
+
After that one-time setup, future releases can be fully automated from Git tags.
|
|
126
|
+
|
|
127
|
+
If you cloned the repository before the rename, update your local remote:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git remote set-url origin https://github.com/EthanBerlant/nim-sync.git
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Releasing a New Version
|
|
134
|
+
|
|
135
|
+
The easiest flow is:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm version patch
|
|
139
|
+
git push origin HEAD --follow-tags
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use `npm version minor` or `npm version major` when appropriate.
|
|
143
|
+
|
|
144
|
+
If you prefer to create the tag manually instead of using `npm version`, this works too:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm version --no-git-tag-version 1.0.1
|
|
148
|
+
git add package.json package-lock.json
|
|
149
|
+
git commit -m "Release 1.0.1"
|
|
150
|
+
git tag v1.0.1
|
|
151
|
+
git push origin HEAD
|
|
152
|
+
git push origin v1.0.1
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### What the Workflow Does
|
|
156
|
+
|
|
157
|
+
On every pushed `v*` tag, GitHub Actions:
|
|
158
|
+
- Verifies the pushed tag matches `package.json`
|
|
159
|
+
- Installs dependencies with `npm ci`
|
|
160
|
+
- Runs the test suite
|
|
161
|
+
- Runs lint and typecheck
|
|
162
|
+
- Builds the package
|
|
163
|
+
- Publishes the package to npm using GitHub OIDC trusted publishing
|
|
164
|
+
|
|
165
|
+
## Test-Driven Development
|
|
166
|
+
|
|
167
|
+
This project follows strict TDD principles:
|
|
168
|
+
|
|
169
|
+
1. **Tests First**: All functionality has failing tests written first
|
|
170
|
+
2. **80%+ Coverage**: Unit, integration, and user journey tests
|
|
171
|
+
3. **User Journeys**: Tests based on actual user scenarios
|
|
172
|
+
4. **Red-Green-Refactor**: Standard TDD workflow
|
|
173
|
+
|
|
174
|
+
## Architecture
|
|
175
|
+
|
|
176
|
+
### File Structure
|
|
177
|
+
```
|
|
178
|
+
src/
|
|
179
|
+
├── index.ts # Plugin entry point
|
|
180
|
+
├── plugin/nim-sync.ts # Main plugin implementation
|
|
181
|
+
├── lib/file-utils.ts # File operations with JSONC support
|
|
182
|
+
├── types/index.ts # TypeScript definitions
|
|
183
|
+
└── __tests__/ # Test suites
|
|
184
|
+
|
|
185
|
+
scripts/
|
|
186
|
+
├── clean.mjs # Cross-platform dist cleanup
|
|
187
|
+
└── bundle.mjs # Standalone plugin bundling
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Key Components
|
|
191
|
+
- **Plugin Initialization**: Runs async refresh on OpenCode startup
|
|
192
|
+
- **Credential Resolution**: Checks `/connect` auth or env var
|
|
193
|
+
- **NVIDIA API Client**: Fetches `/v1/models` endpoint
|
|
194
|
+
- **Config Management**: Atomically updates OpenCode config
|
|
195
|
+
- **Cache System**: 24-hour TTL to avoid excessive API calls
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;wBAGd,MAAM;AAAtC,wBAAsC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,eAAe,aAAuB,CAAA"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for atomic file write operations.
|
|
3
|
+
*/
|
|
4
|
+
export interface AtomicWriteOptions {
|
|
5
|
+
/** Whether to create a backup before overwriting */
|
|
6
|
+
backup?: boolean;
|
|
7
|
+
/** Whether to create backup directory if it doesn't exist */
|
|
8
|
+
createBackupDir?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Timeout for NVIDIA API requests in milliseconds.
|
|
12
|
+
*/
|
|
13
|
+
export declare const API_TIMEOUT_MS = 30000;
|
|
14
|
+
/**
|
|
15
|
+
* Time-to-live for cached model data in milliseconds (24 hours).
|
|
16
|
+
*/
|
|
17
|
+
export declare const CACHE_TTL_MS: number;
|
|
18
|
+
/**
|
|
19
|
+
* Threshold for considering a lock stale in milliseconds (5 minutes).
|
|
20
|
+
*/
|
|
21
|
+
export declare const LOCK_STALE_THRESHOLD_MS: number;
|
|
22
|
+
/**
|
|
23
|
+
* Interval between lock acquisition retry attempts in milliseconds.
|
|
24
|
+
*/
|
|
25
|
+
export declare const LOCK_RETRY_INTERVAL_MS = 100;
|
|
26
|
+
/**
|
|
27
|
+
* Minimum interval between manual refresh operations in milliseconds (60 seconds).
|
|
28
|
+
*/
|
|
29
|
+
export declare const MIN_MANUAL_REFRESH_INTERVAL_MS = 60000;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum number of backup files to retain.
|
|
32
|
+
*/
|
|
33
|
+
export declare const MAX_BACKUPS = 5;
|
|
34
|
+
/**
|
|
35
|
+
* Reads and parses a JSONC (JSON with Comments) file.
|
|
36
|
+
*
|
|
37
|
+
* @param filePath - Path to the JSONC file
|
|
38
|
+
* @param validate - Optional validation function to ensure type safety
|
|
39
|
+
* @returns Parsed content of type T, or empty object if file doesn't exist
|
|
40
|
+
* @throws Error if file read fails (except ENOENT) or if validation fails
|
|
41
|
+
*/
|
|
42
|
+
export declare function readJSONC<T = unknown>(filePath: string, validate?: (data: unknown) => data is T): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Writes data to a JSONC file with atomic operations.
|
|
45
|
+
*
|
|
46
|
+
* @param filePath - Path to write the file
|
|
47
|
+
* @param data - Data to serialize as JSON
|
|
48
|
+
* @param options - Optional backup and directory creation settings
|
|
49
|
+
*/
|
|
50
|
+
export declare function writeJSONC<T = unknown>(filePath: string, data: T, options?: AtomicWriteOptions): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Updates a specific path within a JSONC file while preserving unrelated
|
|
53
|
+
* comments and formatting.
|
|
54
|
+
*
|
|
55
|
+
* @param filePath - Path to the JSONC file
|
|
56
|
+
* @param jsonPath - JSON path to update
|
|
57
|
+
* @param data - Value to write at the given path
|
|
58
|
+
* @param options - Optional backup and directory creation settings
|
|
59
|
+
*/
|
|
60
|
+
export declare function updateJSONCPath<T = unknown>(filePath: string, jsonPath: Array<string | number>, data: T, options?: AtomicWriteOptions): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Atomically writes content to a file using temp file + rename pattern.
|
|
63
|
+
* Optionally creates backups before overwriting and cleans up old backups.
|
|
64
|
+
*
|
|
65
|
+
* @param filePath - Path to write the file
|
|
66
|
+
* @param content - String content to write
|
|
67
|
+
* @param options - Optional backup and directory creation settings
|
|
68
|
+
*/
|
|
69
|
+
export declare function atomicWrite(filePath: string, content: string, options?: AtomicWriteOptions): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Ensures a directory exists, creating it recursively if needed.
|
|
72
|
+
*
|
|
73
|
+
* @param dirPath - Path to the directory
|
|
74
|
+
*/
|
|
75
|
+
export declare function ensureDir(dirPath: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Returns the platform-specific config directory path.
|
|
78
|
+
*
|
|
79
|
+
* @returns Config directory path
|
|
80
|
+
*/
|
|
81
|
+
export declare function getConfigDir(): string;
|
|
82
|
+
/**
|
|
83
|
+
* Returns the platform-specific cache directory path.
|
|
84
|
+
*
|
|
85
|
+
* @returns Cache directory path
|
|
86
|
+
*/
|
|
87
|
+
export declare function getCacheDir(): string;
|
|
88
|
+
/**
|
|
89
|
+
* Returns the platform-specific data directory path.
|
|
90
|
+
*
|
|
91
|
+
* @returns Data directory path
|
|
92
|
+
*/
|
|
93
|
+
export declare function getDataDir(): string;
|
|
94
|
+
/**
|
|
95
|
+
* Acquires an exclusive lock for coordinating file operations.
|
|
96
|
+
* Automatically cleans up stale locks from crashed processes.
|
|
97
|
+
*
|
|
98
|
+
* @param lockName - Name of the lock
|
|
99
|
+
* @param timeoutMs - Maximum time to wait for lock acquisition (default: 5000ms)
|
|
100
|
+
* @returns Function to release the lock
|
|
101
|
+
* @throws Error if lock cannot be acquired within timeout
|
|
102
|
+
*/
|
|
103
|
+
export declare function acquireLock(lockName: string, timeoutMs?: number): Promise<() => Promise<void>>;
|
|
104
|
+
//# sourceMappingURL=file-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/lib/file-utils.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,QAAS,CAAA;AAEpC;;GAEG;AACH,eAAO,MAAM,YAAY,QAAsB,CAAA;AAE/C;;GAEG;AACH,eAAO,MAAM,uBAAuB,QAAgB,CAAA;AAEpD;;GAEG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAA;AAEzC;;GAEG;AACH,eAAO,MAAM,8BAA8B,QAAS,CAAA;AAEpD;;GAEG;AACH,eAAO,MAAM,WAAW,IAAI,CAAA;AAE5B;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,CAAC,GAAG,OAAO,EACzC,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,IAAI,CAAC,GACtC,OAAO,CAAC,CAAC,CAAC,CAwBZ;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,CAAC,GAAG,OAAO,EAC1C,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAC/C,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,EAChC,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAsCf;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ9D;AA+BD;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA8CD;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAuDlG"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { applyEdits, modify as modifyJSONC, parse as parseJSONC } from 'jsonc-parser/lib/esm/main.js';
|
|
4
|
+
/**
|
|
5
|
+
* Timeout for NVIDIA API requests in milliseconds.
|
|
6
|
+
*/
|
|
7
|
+
export const API_TIMEOUT_MS = 30_000;
|
|
8
|
+
/**
|
|
9
|
+
* Time-to-live for cached model data in milliseconds (24 hours).
|
|
10
|
+
*/
|
|
11
|
+
export const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
12
|
+
/**
|
|
13
|
+
* Threshold for considering a lock stale in milliseconds (5 minutes).
|
|
14
|
+
*/
|
|
15
|
+
export const LOCK_STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
16
|
+
/**
|
|
17
|
+
* Interval between lock acquisition retry attempts in milliseconds.
|
|
18
|
+
*/
|
|
19
|
+
export const LOCK_RETRY_INTERVAL_MS = 100;
|
|
20
|
+
/**
|
|
21
|
+
* Minimum interval between manual refresh operations in milliseconds (60 seconds).
|
|
22
|
+
*/
|
|
23
|
+
export const MIN_MANUAL_REFRESH_INTERVAL_MS = 60_000;
|
|
24
|
+
/**
|
|
25
|
+
* Maximum number of backup files to retain.
|
|
26
|
+
*/
|
|
27
|
+
export const MAX_BACKUPS = 5;
|
|
28
|
+
/**
|
|
29
|
+
* Reads and parses a JSONC (JSON with Comments) file.
|
|
30
|
+
*
|
|
31
|
+
* @param filePath - Path to the JSONC file
|
|
32
|
+
* @param validate - Optional validation function to ensure type safety
|
|
33
|
+
* @returns Parsed content of type T, or empty object if file doesn't exist
|
|
34
|
+
* @throws Error if file read fails (except ENOENT) or if validation fails
|
|
35
|
+
*/
|
|
36
|
+
export async function readJSONC(filePath, validate) {
|
|
37
|
+
try {
|
|
38
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
39
|
+
const errors = [];
|
|
40
|
+
const result = parseJSONC(content, errors);
|
|
41
|
+
if (errors.length > 0) {
|
|
42
|
+
const errorDetails = errors
|
|
43
|
+
.map(e => `Parse error code ${e.error} at offset ${e.offset}`)
|
|
44
|
+
.join('; ');
|
|
45
|
+
throw new Error(`JSONC parse errors in ${filePath}: ${errorDetails}`);
|
|
46
|
+
}
|
|
47
|
+
if (validate && !validate(result)) {
|
|
48
|
+
throw new Error(`Invalid data structure in ${filePath}`);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error.code === 'ENOENT') {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Writes data to a JSONC file with atomic operations.
|
|
61
|
+
*
|
|
62
|
+
* @param filePath - Path to write the file
|
|
63
|
+
* @param data - Data to serialize as JSON
|
|
64
|
+
* @param options - Optional backup and directory creation settings
|
|
65
|
+
*/
|
|
66
|
+
export async function writeJSONC(filePath, data, options) {
|
|
67
|
+
const content = JSON.stringify(data, null, 2);
|
|
68
|
+
await atomicWrite(filePath, content, options);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Updates a specific path within a JSONC file while preserving unrelated
|
|
72
|
+
* comments and formatting.
|
|
73
|
+
*
|
|
74
|
+
* @param filePath - Path to the JSONC file
|
|
75
|
+
* @param jsonPath - JSON path to update
|
|
76
|
+
* @param data - Value to write at the given path
|
|
77
|
+
* @param options - Optional backup and directory creation settings
|
|
78
|
+
*/
|
|
79
|
+
export async function updateJSONCPath(filePath, jsonPath, data, options) {
|
|
80
|
+
let existingContent = '';
|
|
81
|
+
try {
|
|
82
|
+
existingContent = await fs.readFile(filePath, 'utf-8');
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error.code !== 'ENOENT') {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const eol = existingContent.includes('\r\n') ? '\r\n' : '\n';
|
|
90
|
+
const edits = modifyJSONC(existingContent, jsonPath, data, {
|
|
91
|
+
formattingOptions: {
|
|
92
|
+
insertSpaces: true,
|
|
93
|
+
tabSize: 2,
|
|
94
|
+
eol
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const updatedContent = applyEdits(existingContent, edits);
|
|
98
|
+
await atomicWrite(filePath, updatedContent, options);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Atomically writes content to a file using temp file + rename pattern.
|
|
102
|
+
* Optionally creates backups before overwriting and cleans up old backups.
|
|
103
|
+
*
|
|
104
|
+
* @param filePath - Path to write the file
|
|
105
|
+
* @param content - String content to write
|
|
106
|
+
* @param options - Optional backup and directory creation settings
|
|
107
|
+
*/
|
|
108
|
+
export async function atomicWrite(filePath, content, options = {}) {
|
|
109
|
+
const dir = path.dirname(filePath);
|
|
110
|
+
const tempPath = `${filePath}.${Date.now()}.tmp`;
|
|
111
|
+
try {
|
|
112
|
+
await fs.mkdir(dir, { recursive: true });
|
|
113
|
+
if (options.backup) {
|
|
114
|
+
try {
|
|
115
|
+
await fs.access(filePath);
|
|
116
|
+
const backupDir = path.join(dir, 'backups');
|
|
117
|
+
if (options.createBackupDir) {
|
|
118
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
const backupPath = path.join(backupDir, `${path.basename(filePath)}.${Date.now()}.bak`);
|
|
121
|
+
await fs.copyFile(filePath, backupPath);
|
|
122
|
+
// Clean up old backups after creating new one
|
|
123
|
+
await cleanupOldBackups(backupDir, path.basename(filePath));
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error.code !== 'ENOENT') {
|
|
127
|
+
throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
await fs.writeFile(tempPath, content, 'utf-8');
|
|
132
|
+
await fs.rename(tempPath, filePath);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
try {
|
|
136
|
+
await fs.unlink(tempPath);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Ignore temp file cleanup failures
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Ensures a directory exists, creating it recursively if needed.
|
|
146
|
+
*
|
|
147
|
+
* @param dirPath - Path to the directory
|
|
148
|
+
*/
|
|
149
|
+
export async function ensureDir(dirPath) {
|
|
150
|
+
try {
|
|
151
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error.code !== 'EEXIST') {
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Returns platform-specific paths for config, data, and cache directories.
|
|
161
|
+
* Handles Windows, Linux, and macOS path conventions.
|
|
162
|
+
*
|
|
163
|
+
* @returns Platform-specific directory paths
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const paths = getPlatformPaths()
|
|
167
|
+
* // Windows: { config: 'C:\\Users\\username\\AppData\\Roaming\\opencode', ... }
|
|
168
|
+
* // Linux: { config: '/home/username/.config/opencode', ... }
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
function getPlatformPaths() {
|
|
172
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
173
|
+
const isWindows = process.platform === 'win32';
|
|
174
|
+
return {
|
|
175
|
+
config: isWindows
|
|
176
|
+
? path.join(home, 'AppData', 'Roaming', 'opencode')
|
|
177
|
+
: path.join(home, '.config', 'opencode'),
|
|
178
|
+
data: isWindows
|
|
179
|
+
? path.join(home, 'AppData', 'Roaming', 'opencode')
|
|
180
|
+
: path.join(home, '.local', 'share', 'opencode'),
|
|
181
|
+
cache: isWindows
|
|
182
|
+
? path.join(home, 'AppData', 'Local', 'opencode', 'cache')
|
|
183
|
+
: path.join(home, '.cache', 'opencode')
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Returns the platform-specific config directory path.
|
|
188
|
+
*
|
|
189
|
+
* @returns Config directory path
|
|
190
|
+
*/
|
|
191
|
+
export function getConfigDir() {
|
|
192
|
+
return getPlatformPaths().config;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Returns the platform-specific cache directory path.
|
|
196
|
+
*
|
|
197
|
+
* @returns Cache directory path
|
|
198
|
+
*/
|
|
199
|
+
export function getCacheDir() {
|
|
200
|
+
return getPlatformPaths().cache;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Returns the platform-specific data directory path.
|
|
204
|
+
*
|
|
205
|
+
* @returns Data directory path
|
|
206
|
+
*/
|
|
207
|
+
export function getDataDir() {
|
|
208
|
+
return getPlatformPaths().data;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Checks if a process with the given PID is currently running.
|
|
212
|
+
* Uses a non-destructive signal (0) to check process existence.
|
|
213
|
+
*
|
|
214
|
+
* @param pid - Process ID to check
|
|
215
|
+
* @returns true if the process exists, false otherwise
|
|
216
|
+
*/
|
|
217
|
+
function isProcessRunning(pid) {
|
|
218
|
+
try {
|
|
219
|
+
// Signal 0 doesn't kill the process, just checks if it exists
|
|
220
|
+
process.kill(pid, 0);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Cleans up old backup files, keeping only the most recent MAX_BACKUPS.
|
|
229
|
+
*
|
|
230
|
+
* @param backupDir - Directory containing backup files
|
|
231
|
+
* @param baseName - Base name of the file being backed up
|
|
232
|
+
*/
|
|
233
|
+
async function cleanupOldBackups(backupDir, baseName) {
|
|
234
|
+
try {
|
|
235
|
+
const files = await fs.readdir(backupDir);
|
|
236
|
+
const backups = files
|
|
237
|
+
.filter(f => f.startsWith(baseName) && f.endsWith('.bak'))
|
|
238
|
+
.map(f => ({
|
|
239
|
+
name: f,
|
|
240
|
+
// Extract timestamp from filename like "file.json.1234567890.bak"
|
|
241
|
+
timestamp: parseInt(f.split('.').slice(-2, -1)[0]) || 0
|
|
242
|
+
}))
|
|
243
|
+
.sort((a, b) => b.timestamp - a.timestamp); // Sort newest first
|
|
244
|
+
// Keep only the most recent backups
|
|
245
|
+
for (const oldBackup of backups.slice(MAX_BACKUPS)) {
|
|
246
|
+
await fs.unlink(path.join(backupDir, oldBackup.name));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Ignore cleanup failures - don't want to fail the main operation
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Acquires an exclusive lock for coordinating file operations.
|
|
255
|
+
* Automatically cleans up stale locks from crashed processes.
|
|
256
|
+
*
|
|
257
|
+
* @param lockName - Name of the lock
|
|
258
|
+
* @param timeoutMs - Maximum time to wait for lock acquisition (default: 5000ms)
|
|
259
|
+
* @returns Function to release the lock
|
|
260
|
+
* @throws Error if lock cannot be acquired within timeout
|
|
261
|
+
*/
|
|
262
|
+
export async function acquireLock(lockName, timeoutMs = 5000) {
|
|
263
|
+
const lockDir = getCacheDir();
|
|
264
|
+
const lockPath = path.join(lockDir, `${lockName}.lock`);
|
|
265
|
+
await ensureDir(lockDir);
|
|
266
|
+
try {
|
|
267
|
+
const lockContent = await fs.readFile(lockPath, 'utf-8');
|
|
268
|
+
const metadata = JSON.parse(lockContent);
|
|
269
|
+
const staleThreshold = Date.now() - LOCK_STALE_THRESHOLD_MS;
|
|
270
|
+
// Check if timestamp is stale OR if the holding process is no longer running
|
|
271
|
+
const isStale = metadata.timestamp < staleThreshold;
|
|
272
|
+
const processExists = metadata.pid ? isProcessRunning(metadata.pid) : true;
|
|
273
|
+
// Note: TOCTOU race possible between check and delete, but mitigated by
|
|
274
|
+
// atomic 'wx' flag in subsequent fs.open() call which provides ultimate protection
|
|
275
|
+
if (isStale || !processExists) {
|
|
276
|
+
await fs.unlink(lockPath);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
if (error.code !== 'ENOENT') {
|
|
281
|
+
console.error('Failed to clean up stale lock');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const startTime = Date.now();
|
|
285
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
286
|
+
try {
|
|
287
|
+
const fd = await fs.open(lockPath, 'wx');
|
|
288
|
+
const metadata = {
|
|
289
|
+
pid: process.pid,
|
|
290
|
+
timestamp: Date.now()
|
|
291
|
+
};
|
|
292
|
+
await fd.writeFile(JSON.stringify(metadata));
|
|
293
|
+
await fd.close();
|
|
294
|
+
return async () => {
|
|
295
|
+
try {
|
|
296
|
+
await fs.unlink(lockPath);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// Ignore unlock failures
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (error.code !== 'EEXIST') {
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
await new Promise(resolve => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`Failed to acquire lock "${lockName}" after ${timeoutMs}ms`);
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=file-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/lib/file-utils.ts"],"names":[],"mappings":"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"}
|