opencode-onboard 0.4.8 → 0.4.9
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-onboard",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Prepare any brownfield codebase for AI agent workflows using OpenCode, OpenSpec, and ensemble orchestration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"chalk": "^5.0.0",
|
|
31
31
|
"execa": "^9.6.1",
|
|
32
32
|
"fs-extra": "^11.0.0",
|
|
33
|
+
"jsonc-parser": "^3.3.1",
|
|
33
34
|
"ora": "^8.0.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
import { execa } from 'execa'
|
|
2
2
|
import fse from 'fs-extra'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
+
import { parse as parseJsonc } from 'jsonc-parser'
|
|
4
5
|
import { header, success, warn, error, loading } from '../../utils/exec.js'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* After codegraph install, it may create an `opencode.jsonc` at the project root.
|
|
8
9
|
* This project uses `.opencode/opencode.json` instead. Merge any MCP config from
|
|
9
10
|
* the rogue file into the correct location and remove it.
|
|
11
|
+
* Returns true if config was successfully merged (or no rogue file existed), false on parse failure.
|
|
10
12
|
*/
|
|
11
13
|
export async function fixCodegraphConfig() {
|
|
12
14
|
const cwd = process.cwd()
|
|
13
15
|
const rogueFile = path.join(cwd, 'opencode.jsonc')
|
|
14
16
|
const correctFile = path.join(cwd, '.opencode', 'opencode.json')
|
|
15
17
|
|
|
16
|
-
if (!await fse.pathExists(rogueFile)) return
|
|
18
|
+
if (!await fse.pathExists(rogueFile)) return true
|
|
17
19
|
|
|
18
20
|
let rogueContent
|
|
19
21
|
try {
|
|
20
22
|
const raw = await fse.readFile(rogueFile, 'utf-8')
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const errors = []
|
|
24
|
+
rogueContent = parseJsonc(raw, errors)
|
|
25
|
+
if (errors.length > 0) throw new Error(`parse errors: ${errors.length}`)
|
|
26
|
+
if (!rogueContent || typeof rogueContent !== 'object' || Array.isArray(rogueContent)) {
|
|
27
|
+
throw new Error('unexpected structure')
|
|
28
|
+
}
|
|
26
29
|
} catch {
|
|
27
30
|
warn('Could not parse opencode.jsonc, removing it')
|
|
28
31
|
await fse.remove(rogueFile)
|
|
29
|
-
return
|
|
32
|
+
return false
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
let correctContent = {}
|
|
@@ -34,11 +37,10 @@ export async function fixCodegraphConfig() {
|
|
|
34
37
|
try {
|
|
35
38
|
correctContent = await fse.readJson(correctFile)
|
|
36
39
|
} catch {
|
|
37
|
-
|
|
40
|
+
// ignore invalid existing config
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
// Merge mcpServers from rogue into correct config
|
|
42
44
|
if (rogueContent.mcpServers || rogueContent.mcp) {
|
|
43
45
|
const mcpServers = rogueContent.mcpServers || rogueContent.mcp
|
|
44
46
|
correctContent.mcpServers = { ...(correctContent.mcpServers || {}), ...mcpServers }
|
|
@@ -48,6 +50,8 @@ export async function fixCodegraphConfig() {
|
|
|
48
50
|
await fse.writeJson(correctFile, correctContent, { spaces: 2 })
|
|
49
51
|
await fse.remove(rogueFile)
|
|
50
52
|
warn('Migrated codegraph config from opencode.jsonc → .opencode/opencode.json (removed opencode.jsonc)')
|
|
53
|
+
|
|
54
|
+
return true
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
export async function installCodegraph(options = {}) {
|
|
@@ -73,7 +77,13 @@ export async function installCodegraph(options = {}) {
|
|
|
73
77
|
return { optedIn: true, installed: false }
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
await fixCodegraphConfig()
|
|
80
|
+
const configFixed = await fixCodegraphConfig()
|
|
81
|
+
|
|
82
|
+
if (!configFixed) {
|
|
83
|
+
warn('codegraph config could not be merged — skipping init')
|
|
84
|
+
return { optedIn: true, installed: false }
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
success(`codegraph configured for opencode (${location})`)
|
|
78
88
|
} catch (err) {
|
|
79
89
|
error(`Failed to install codegraph: ${err.message}`)
|
|
@@ -29,9 +29,9 @@ describe('fixCodegraphConfig()', () => {
|
|
|
29
29
|
vi.restoreAllMocks()
|
|
30
30
|
})
|
|
31
31
|
|
|
32
|
-
it('
|
|
33
|
-
await fixCodegraphConfig()
|
|
34
|
-
|
|
32
|
+
it('returns true when opencode.jsonc does not exist', async () => {
|
|
33
|
+
const result = await fixCodegraphConfig()
|
|
34
|
+
expect(result).toBe(true)
|
|
35
35
|
expect(fs.existsSync(path.join(tmpDir, '.opencode', 'opencode.json'))).toBe(false)
|
|
36
36
|
})
|
|
37
37
|
|
|
@@ -50,12 +50,13 @@ describe('fixCodegraphConfig()', () => {
|
|
|
50
50
|
"plugin": ["opencode-plugin-openspec@latest"]
|
|
51
51
|
}))
|
|
52
52
|
|
|
53
|
-
await fixCodegraphConfig()
|
|
53
|
+
const result = await fixCodegraphConfig()
|
|
54
54
|
|
|
55
|
+
expect(result).toBe(true)
|
|
55
56
|
expect(fs.existsSync(path.join(tmpDir, 'opencode.jsonc'))).toBe(false)
|
|
56
|
-
const
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
57
|
+
const readResult = await fse.readJson(path.join(opencodeDir, 'opencode.json'))
|
|
58
|
+
expect(readResult.mcpServers.codegraph).toEqual({ command: 'codegraph', args: ['mcp'] })
|
|
59
|
+
expect(readResult.plugin).toEqual(["opencode-plugin-openspec@latest"])
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
it('handles JSONC with comments', async () => {
|
|
@@ -71,18 +72,43 @@ describe('fixCodegraphConfig()', () => {
|
|
|
71
72
|
fs.mkdirSync(opencodeDir, { recursive: true })
|
|
72
73
|
fs.writeFileSync(path.join(opencodeDir, 'opencode.json'), '{}')
|
|
73
74
|
|
|
74
|
-
await fixCodegraphConfig()
|
|
75
|
+
const result = await fixCodegraphConfig()
|
|
75
76
|
|
|
77
|
+
expect(result).toBe(true)
|
|
76
78
|
expect(fs.existsSync(path.join(tmpDir, 'opencode.jsonc'))).toBe(false)
|
|
77
|
-
const
|
|
78
|
-
expect(
|
|
79
|
+
const readResult = await fse.readJson(path.join(opencodeDir, 'opencode.json'))
|
|
80
|
+
expect(readResult.mcpServers.codegraph.command).toBe('codegraph')
|
|
79
81
|
})
|
|
80
82
|
|
|
81
|
-
it('
|
|
83
|
+
it('handles JSONC with URLs containing //', async () => {
|
|
84
|
+
const rogueRaw = `{
|
|
85
|
+
"url": "https://example.com/path",
|
|
86
|
+
"mcpServers": {
|
|
87
|
+
"codegraph": { "command": "codegraph", "args": ["mcp"] }
|
|
88
|
+
}
|
|
89
|
+
}`
|
|
90
|
+
fs.writeFileSync(path.join(tmpDir, 'opencode.jsonc'), rogueRaw)
|
|
91
|
+
|
|
92
|
+
const opencodeDir = path.join(tmpDir, '.opencode')
|
|
93
|
+
fs.mkdirSync(opencodeDir, { recursive: true })
|
|
94
|
+
fs.writeFileSync(path.join(opencodeDir, 'opencode.json'), '{}')
|
|
95
|
+
|
|
96
|
+
const result = await fixCodegraphConfig()
|
|
97
|
+
|
|
98
|
+
// The old regex-based parser would have mangled the URL and failed.
|
|
99
|
+
// jsonc-parser handles this correctly.
|
|
100
|
+
expect(result).toBe(true)
|
|
101
|
+
expect(fs.existsSync(path.join(tmpDir, 'opencode.jsonc'))).toBe(false)
|
|
102
|
+
const readResult = await fse.readJson(path.join(opencodeDir, 'opencode.json'))
|
|
103
|
+
expect(readResult.mcpServers.codegraph.command).toBe('codegraph')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('removes unparseable opencode.jsonc, warns, and returns false', async () => {
|
|
82
107
|
fs.writeFileSync(path.join(tmpDir, 'opencode.jsonc'), 'not valid json {{{')
|
|
83
108
|
|
|
84
|
-
await fixCodegraphConfig()
|
|
109
|
+
const result = await fixCodegraphConfig()
|
|
85
110
|
|
|
111
|
+
expect(result).toBe(false)
|
|
86
112
|
expect(fs.existsSync(path.join(tmpDir, 'opencode.jsonc'))).toBe(false)
|
|
87
113
|
expect(warn).toHaveBeenCalledWith('Could not parse opencode.jsonc, removing it')
|
|
88
114
|
})
|
|
@@ -95,10 +121,11 @@ describe('fixCodegraphConfig()', () => {
|
|
|
95
121
|
}
|
|
96
122
|
fs.writeFileSync(path.join(tmpDir, 'opencode.jsonc'), JSON.stringify(rogueContent))
|
|
97
123
|
|
|
98
|
-
await fixCodegraphConfig()
|
|
124
|
+
const result = await fixCodegraphConfig()
|
|
99
125
|
|
|
100
|
-
|
|
101
|
-
|
|
126
|
+
expect(result).toBe(true)
|
|
127
|
+
const readResult = await fse.readJson(path.join(tmpDir, '.opencode', 'opencode.json'))
|
|
128
|
+
expect(readResult.mcpServers.codegraph.command).toBe('codegraph')
|
|
102
129
|
expect(fs.existsSync(path.join(tmpDir, 'opencode.jsonc'))).toBe(false)
|
|
103
130
|
})
|
|
104
131
|
})
|