kanban-lite 1.0.9 → 1.0.13
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/dist/cli.js +1015 -407
- package/dist/extension.js +897 -347
- package/dist/mcp-server.js +550 -189
- package/dist/sdk/index.cjs +1223 -0
- package/dist/sdk/index.mjs +1168 -0
- package/dist/sdk/sdk/KanbanSDK.d.ts +39 -21
- package/dist/sdk/sdk/index.d.ts +4 -3
- package/dist/sdk/sdk/migration.d.ts +6 -0
- package/dist/sdk/sdk/types.d.ts +3 -5
- package/dist/sdk/shared/config.d.ts +23 -10
- package/dist/sdk/shared/types.d.ts +18 -4
- package/dist/standalone-webview/index.js +27 -27
- package/dist/standalone-webview/index.js.map +1 -1
- package/dist/standalone-webview/style.css +1 -1
- package/dist/standalone.js +831 -328
- package/dist/webview/index.js +45 -45
- package/dist/webview/index.js.map +1 -1
- package/dist/webview/style.css +1 -1
- package/package.json +4 -3
- package/src/cli/index.ts +217 -95
- package/src/extension/KanbanPanel.ts +49 -22
- package/src/mcp-server/index.ts +138 -62
- package/src/sdk/KanbanSDK.ts +283 -77
- package/src/sdk/__tests__/KanbanSDK.test.ts +5 -5
- package/src/sdk/__tests__/migration.test.ts +269 -0
- package/src/sdk/__tests__/multi-board.test.ts +449 -0
- package/src/sdk/index.ts +4 -3
- package/src/sdk/migration.ts +52 -0
- package/src/sdk/types.ts +3 -6
- package/src/shared/config.ts +144 -22
- package/src/shared/types.ts +14 -5
- package/src/standalone/__tests__/server.integration.test.ts +38 -37
- package/src/standalone/server.ts +239 -21
- package/src/webview/App.tsx +17 -7
- package/src/webview/components/Toolbar.tsx +99 -3
- package/src/webview/store/index.ts +11 -3
- package/.kanban/backlog/1-test1.md +0 -30
- package/.kanban.json +0 -42
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as os from 'node:os'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import { migrateFileSystemToMultiBoard } from '../migration'
|
|
6
|
+
import { readConfig } from '../../shared/config'
|
|
7
|
+
import { DEFAULT_COLUMNS } from '../../shared/types'
|
|
8
|
+
|
|
9
|
+
function createTempDir(): string {
|
|
10
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'kanban-migration-test-'))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('migrateFileSystemToMultiBoard', () => {
|
|
14
|
+
let workspaceDir: string
|
|
15
|
+
let featuresDir: string
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
workspaceDir = createTempDir()
|
|
19
|
+
featuresDir = path.join(workspaceDir, '.kanban')
|
|
20
|
+
fs.mkdirSync(featuresDir, { recursive: true })
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
fs.rmSync(workspaceDir, { recursive: true, force: true })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should create boards/default/ and move status subdirs into it', async () => {
|
|
28
|
+
// Create v1-style status directories with card files
|
|
29
|
+
const backlogDir = path.join(featuresDir, 'backlog')
|
|
30
|
+
const todoDir = path.join(featuresDir, 'todo')
|
|
31
|
+
const doneDir = path.join(featuresDir, 'done')
|
|
32
|
+
fs.mkdirSync(backlogDir, { recursive: true })
|
|
33
|
+
fs.mkdirSync(todoDir, { recursive: true })
|
|
34
|
+
fs.mkdirSync(doneDir, { recursive: true })
|
|
35
|
+
fs.writeFileSync(path.join(backlogDir, 'card1.md'), '# Card 1', 'utf-8')
|
|
36
|
+
fs.writeFileSync(path.join(todoDir, 'card2.md'), '# Card 2', 'utf-8')
|
|
37
|
+
fs.writeFileSync(path.join(doneDir, 'card3.md'), '# Card 3', 'utf-8')
|
|
38
|
+
|
|
39
|
+
await migrateFileSystemToMultiBoard(featuresDir)
|
|
40
|
+
|
|
41
|
+
// Verify boards/default/ was created
|
|
42
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default'))).toBe(true)
|
|
43
|
+
|
|
44
|
+
// Verify status dirs were moved into boards/default/
|
|
45
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'card1.md'))).toBe(true)
|
|
46
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'todo', 'card2.md'))).toBe(true)
|
|
47
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'done', 'card3.md'))).toBe(true)
|
|
48
|
+
|
|
49
|
+
// Verify old dirs no longer exist at root level
|
|
50
|
+
expect(fs.existsSync(path.join(featuresDir, 'backlog'))).toBe(false)
|
|
51
|
+
expect(fs.existsSync(path.join(featuresDir, 'todo'))).toBe(false)
|
|
52
|
+
expect(fs.existsSync(path.join(featuresDir, 'done'))).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should move root .md files into boards/default/backlog/', async () => {
|
|
56
|
+
// Create root-level markdown files (orphaned cards)
|
|
57
|
+
fs.writeFileSync(path.join(featuresDir, 'orphan1.md'), '# Orphan 1', 'utf-8')
|
|
58
|
+
fs.writeFileSync(path.join(featuresDir, 'orphan2.md'), '# Orphan 2', 'utf-8')
|
|
59
|
+
// Also a non-md file that should not be moved
|
|
60
|
+
fs.writeFileSync(path.join(featuresDir, 'board.json'), '{}', 'utf-8')
|
|
61
|
+
|
|
62
|
+
await migrateFileSystemToMultiBoard(featuresDir)
|
|
63
|
+
|
|
64
|
+
// .md files should be moved to boards/default/backlog/
|
|
65
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'orphan1.md'))).toBe(true)
|
|
66
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'orphan2.md'))).toBe(true)
|
|
67
|
+
|
|
68
|
+
// Non-md files remain at root
|
|
69
|
+
expect(fs.existsSync(path.join(featuresDir, 'board.json'))).toBe(true)
|
|
70
|
+
|
|
71
|
+
// Root .md files should no longer exist
|
|
72
|
+
expect(fs.existsSync(path.join(featuresDir, 'orphan1.md'))).toBe(false)
|
|
73
|
+
expect(fs.existsSync(path.join(featuresDir, 'orphan2.md'))).toBe(false)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should be idempotent - second call is a no-op', async () => {
|
|
77
|
+
// Create a status dir and a root .md file
|
|
78
|
+
const backlogDir = path.join(featuresDir, 'backlog')
|
|
79
|
+
fs.mkdirSync(backlogDir, { recursive: true })
|
|
80
|
+
fs.writeFileSync(path.join(backlogDir, 'card.md'), '# Card', 'utf-8')
|
|
81
|
+
fs.writeFileSync(path.join(featuresDir, 'orphan.md'), '# Orphan', 'utf-8')
|
|
82
|
+
|
|
83
|
+
// First migration
|
|
84
|
+
await migrateFileSystemToMultiBoard(featuresDir)
|
|
85
|
+
|
|
86
|
+
const migratedCardPath = path.join(featuresDir, 'boards', 'default', 'backlog', 'card.md')
|
|
87
|
+
const migratedOrphanPath = path.join(featuresDir, 'boards', 'default', 'backlog', 'orphan.md')
|
|
88
|
+
expect(fs.existsSync(migratedCardPath)).toBe(true)
|
|
89
|
+
expect(fs.existsSync(migratedOrphanPath)).toBe(true)
|
|
90
|
+
|
|
91
|
+
// Record content to verify it doesn't change
|
|
92
|
+
const cardContent = fs.readFileSync(migratedCardPath, 'utf-8')
|
|
93
|
+
const orphanContent = fs.readFileSync(migratedOrphanPath, 'utf-8')
|
|
94
|
+
|
|
95
|
+
// Second migration should be a no-op
|
|
96
|
+
await migrateFileSystemToMultiBoard(featuresDir)
|
|
97
|
+
|
|
98
|
+
expect(fs.existsSync(migratedCardPath)).toBe(true)
|
|
99
|
+
expect(fs.existsSync(migratedOrphanPath)).toBe(true)
|
|
100
|
+
expect(fs.readFileSync(migratedCardPath, 'utf-8')).toBe(cardContent)
|
|
101
|
+
expect(fs.readFileSync(migratedOrphanPath, 'utf-8')).toBe(orphanContent)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should handle non-existent featuresDir gracefully', async () => {
|
|
105
|
+
const nonExistentDir = path.join(workspaceDir, 'nonexistent', '.kanban')
|
|
106
|
+
|
|
107
|
+
// Should not throw
|
|
108
|
+
await expect(migrateFileSystemToMultiBoard(nonExistentDir)).resolves.toBeUndefined()
|
|
109
|
+
|
|
110
|
+
// boards/default/ gets created by mkdir({ recursive: true }) but no status dirs are moved
|
|
111
|
+
// since there were none to begin with, so the default board dir should be empty
|
|
112
|
+
const defaultBoardDir = path.join(nonExistentDir, 'boards', 'default')
|
|
113
|
+
expect(fs.existsSync(defaultBoardDir)).toBe(true)
|
|
114
|
+
const contents = fs.readdirSync(defaultBoardDir)
|
|
115
|
+
expect(contents).toEqual([])
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should skip dot-directories (e.g. .git)', async () => {
|
|
119
|
+
// Create a dot-directory and a normal status directory
|
|
120
|
+
const gitDir = path.join(featuresDir, '.git')
|
|
121
|
+
const backlogDir = path.join(featuresDir, 'backlog')
|
|
122
|
+
fs.mkdirSync(gitDir, { recursive: true })
|
|
123
|
+
fs.mkdirSync(backlogDir, { recursive: true })
|
|
124
|
+
fs.writeFileSync(path.join(gitDir, 'config'), 'git config content', 'utf-8')
|
|
125
|
+
fs.writeFileSync(path.join(backlogDir, 'card.md'), '# Card', 'utf-8')
|
|
126
|
+
|
|
127
|
+
await migrateFileSystemToMultiBoard(featuresDir)
|
|
128
|
+
|
|
129
|
+
// .git directory should remain at root, not moved into boards/default/
|
|
130
|
+
expect(fs.existsSync(path.join(featuresDir, '.git', 'config'))).toBe(true)
|
|
131
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', '.git'))).toBe(false)
|
|
132
|
+
|
|
133
|
+
// backlog should be moved
|
|
134
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'card.md'))).toBe(true)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should handle mixed directories and root .md files together', async () => {
|
|
138
|
+
// Create status dirs, a dot-dir, and root .md files
|
|
139
|
+
const todoDir = path.join(featuresDir, 'todo')
|
|
140
|
+
const reviewDir = path.join(featuresDir, 'review')
|
|
141
|
+
fs.mkdirSync(todoDir, { recursive: true })
|
|
142
|
+
fs.mkdirSync(reviewDir, { recursive: true })
|
|
143
|
+
fs.writeFileSync(path.join(todoDir, 'task.md'), '# Task', 'utf-8')
|
|
144
|
+
fs.writeFileSync(path.join(reviewDir, 'pr.md'), '# PR Review', 'utf-8')
|
|
145
|
+
fs.writeFileSync(path.join(featuresDir, 'loose-card.md'), '# Loose', 'utf-8')
|
|
146
|
+
|
|
147
|
+
await migrateFileSystemToMultiBoard(featuresDir)
|
|
148
|
+
|
|
149
|
+
// Status dirs moved
|
|
150
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'todo', 'task.md'))).toBe(true)
|
|
151
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'review', 'pr.md'))).toBe(true)
|
|
152
|
+
|
|
153
|
+
// Root .md moved to backlog
|
|
154
|
+
expect(fs.existsSync(path.join(featuresDir, 'boards', 'default', 'backlog', 'loose-card.md'))).toBe(true)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
describe('config migration (v1 to v2)', () => {
|
|
159
|
+
let workspaceDir: string
|
|
160
|
+
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
workspaceDir = createTempDir()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
afterEach(() => {
|
|
166
|
+
fs.rmSync(workspaceDir, { recursive: true, force: true })
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('should migrate a v1 config (no version field) to v2 format with boards.default', () => {
|
|
170
|
+
const v1Config = {
|
|
171
|
+
featuresDirectory: '.kanban',
|
|
172
|
+
defaultPriority: 'high',
|
|
173
|
+
defaultStatus: 'todo',
|
|
174
|
+
columns: [
|
|
175
|
+
{ id: 'todo', name: 'To Do', color: '#3b82f6' },
|
|
176
|
+
{ id: 'done', name: 'Done', color: '#22c55e' }
|
|
177
|
+
],
|
|
178
|
+
aiAgent: 'claude',
|
|
179
|
+
nextCardId: 5,
|
|
180
|
+
showPriorityBadges: true,
|
|
181
|
+
showAssignee: false,
|
|
182
|
+
showDueDate: true,
|
|
183
|
+
showLabels: false,
|
|
184
|
+
showBuildWithAI: true,
|
|
185
|
+
showFileName: false,
|
|
186
|
+
compactMode: true,
|
|
187
|
+
markdownEditorMode: false
|
|
188
|
+
}
|
|
189
|
+
fs.writeFileSync(path.join(workspaceDir, '.kanban.json'), JSON.stringify(v1Config), 'utf-8')
|
|
190
|
+
|
|
191
|
+
const config = readConfig(workspaceDir)
|
|
192
|
+
|
|
193
|
+
// Should be v2 format
|
|
194
|
+
expect(config.version).toBe(2)
|
|
195
|
+
expect(config.defaultBoard).toBe('default')
|
|
196
|
+
expect(config.boards).toBeDefined()
|
|
197
|
+
expect(config.boards.default).toBeDefined()
|
|
198
|
+
|
|
199
|
+
// Board config should carry over v1 values
|
|
200
|
+
const board = config.boards.default
|
|
201
|
+
expect(board.name).toBe('Default')
|
|
202
|
+
expect(board.columns).toEqual(v1Config.columns)
|
|
203
|
+
expect(board.nextCardId).toBe(5)
|
|
204
|
+
expect(board.defaultStatus).toBe('todo')
|
|
205
|
+
expect(board.defaultPriority).toBe('high')
|
|
206
|
+
|
|
207
|
+
// Global settings should carry over
|
|
208
|
+
expect(config.showAssignee).toBe(false)
|
|
209
|
+
expect(config.compactMode).toBe(true)
|
|
210
|
+
expect(config.showLabels).toBe(false)
|
|
211
|
+
expect(config.featuresDirectory).toBe('.kanban')
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('should migrate a v1 config with explicit version: 1 to v2 format', () => {
|
|
215
|
+
const v1Config = {
|
|
216
|
+
version: 1,
|
|
217
|
+
featuresDirectory: '.kanban',
|
|
218
|
+
defaultPriority: 'medium',
|
|
219
|
+
defaultStatus: 'backlog',
|
|
220
|
+
columns: DEFAULT_COLUMNS,
|
|
221
|
+
aiAgent: 'claude',
|
|
222
|
+
nextCardId: 1,
|
|
223
|
+
showPriorityBadges: true,
|
|
224
|
+
showAssignee: true,
|
|
225
|
+
showDueDate: true,
|
|
226
|
+
showLabels: true,
|
|
227
|
+
showBuildWithAI: true,
|
|
228
|
+
showFileName: false,
|
|
229
|
+
compactMode: false,
|
|
230
|
+
markdownEditorMode: false
|
|
231
|
+
}
|
|
232
|
+
fs.writeFileSync(path.join(workspaceDir, '.kanban.json'), JSON.stringify(v1Config), 'utf-8')
|
|
233
|
+
|
|
234
|
+
const config = readConfig(workspaceDir)
|
|
235
|
+
|
|
236
|
+
expect(config.version).toBe(2)
|
|
237
|
+
expect(config.boards.default).toBeDefined()
|
|
238
|
+
expect(config.boards.default.columns).toEqual(DEFAULT_COLUMNS)
|
|
239
|
+
expect(config.boards.default.nextCardId).toBe(1)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('should persist the migrated v2 config to disk', () => {
|
|
243
|
+
const v1Config = {
|
|
244
|
+
featuresDirectory: '.kanban',
|
|
245
|
+
columns: [{ id: 'backlog', name: 'Backlog', color: '#6b7280' }],
|
|
246
|
+
nextCardId: 3
|
|
247
|
+
}
|
|
248
|
+
fs.writeFileSync(path.join(workspaceDir, '.kanban.json'), JSON.stringify(v1Config), 'utf-8')
|
|
249
|
+
|
|
250
|
+
// First read triggers migration
|
|
251
|
+
readConfig(workspaceDir)
|
|
252
|
+
|
|
253
|
+
// Second read should load the persisted v2 config directly
|
|
254
|
+
const raw = JSON.parse(fs.readFileSync(path.join(workspaceDir, '.kanban.json'), 'utf-8'))
|
|
255
|
+
expect(raw.version).toBe(2)
|
|
256
|
+
expect(raw.boards).toBeDefined()
|
|
257
|
+
expect(raw.boards.default).toBeDefined()
|
|
258
|
+
expect(raw.boards.default.columns).toEqual([{ id: 'backlog', name: 'Backlog', color: '#6b7280' }])
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('should return default config when no .kanban.json exists', () => {
|
|
262
|
+
const config = readConfig(workspaceDir)
|
|
263
|
+
|
|
264
|
+
expect(config.version).toBe(2)
|
|
265
|
+
expect(config.boards.default).toBeDefined()
|
|
266
|
+
expect(config.boards.default.columns).toEqual(DEFAULT_COLUMNS)
|
|
267
|
+
expect(config.defaultBoard).toBe('default')
|
|
268
|
+
})
|
|
269
|
+
})
|