team-toon-tack 1.0.4 → 1.0.6
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 +1 -1
- package/scripts/init.ts +16 -7
- package/scripts/sync.ts +43 -22
- package/scripts/utils.ts +32 -5
- package/scripts/work-on.ts +4 -4
package/package.json
CHANGED
package/scripts/init.ts
CHANGED
|
@@ -277,17 +277,14 @@ async function init() {
|
|
|
277
277
|
complete: 'Done',
|
|
278
278
|
need_review: 'In Review'
|
|
279
279
|
},
|
|
280
|
+
priority_order: ['urgent', 'high', 'medium', 'low', 'none'],
|
|
280
281
|
current_cycle: currentCycle ? {
|
|
281
282
|
id: currentCycle.id,
|
|
282
283
|
name: currentCycle.name || 'Cycle',
|
|
283
284
|
start_date: currentCycle.startsAt?.toISOString().split('T')[0] || '',
|
|
284
285
|
end_date: currentCycle.endsAt?.toISOString().split('T')[0] || ''
|
|
285
|
-
} :
|
|
286
|
-
|
|
287
|
-
name: 'No active cycle',
|
|
288
|
-
start_date: '',
|
|
289
|
-
end_date: ''
|
|
290
|
-
}
|
|
286
|
+
} : undefined,
|
|
287
|
+
cycle_history: []
|
|
291
288
|
};
|
|
292
289
|
|
|
293
290
|
// Find current user key
|
|
@@ -309,11 +306,23 @@ async function init() {
|
|
|
309
306
|
const existingContent = await fs.readFile(paths.configPath, 'utf-8');
|
|
310
307
|
const existingConfig = decode(existingContent) as unknown as Config;
|
|
311
308
|
|
|
312
|
-
// Merge:
|
|
309
|
+
// Merge: preserve existing custom fields
|
|
313
310
|
config.status_transitions = {
|
|
314
311
|
...existingConfig.status_transitions,
|
|
315
312
|
...config.status_transitions
|
|
316
313
|
};
|
|
314
|
+
// Preserve cycle history
|
|
315
|
+
if (existingConfig.cycle_history) {
|
|
316
|
+
config.cycle_history = existingConfig.cycle_history;
|
|
317
|
+
}
|
|
318
|
+
// Preserve current_cycle if not fetched fresh
|
|
319
|
+
if (!currentCycle && existingConfig.current_cycle) {
|
|
320
|
+
config.current_cycle = existingConfig.current_cycle;
|
|
321
|
+
}
|
|
322
|
+
// Preserve priority_order if exists
|
|
323
|
+
if (existingConfig.priority_order) {
|
|
324
|
+
config.priority_order = existingConfig.priority_order;
|
|
325
|
+
}
|
|
317
326
|
} catch {
|
|
318
327
|
// Ignore merge errors
|
|
319
328
|
}
|
package/scripts/sync.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getLinearClient, loadConfig, loadLocalConfig,
|
|
1
|
+
import { getLinearClient, loadConfig, loadLocalConfig, loadCycleData, saveCycleData, saveConfig, getTeamId, getPrioritySortIndex, CycleData, Task, Attachment, Comment, CycleInfo } from './utils';
|
|
2
2
|
|
|
3
3
|
async function sync() {
|
|
4
4
|
const args = process.argv.slice(2);
|
|
@@ -47,24 +47,46 @@ Examples:
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const activeCycle = cycles.nodes[0];
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
const cycleId = activeCycle.id;
|
|
51
|
+
const cycleName = activeCycle.name ?? 'Cycle';
|
|
52
|
+
const newCycleInfo: CycleInfo = {
|
|
53
|
+
id: cycleId,
|
|
54
|
+
name: cycleName,
|
|
55
|
+
start_date: activeCycle.startsAt?.toISOString().split('T')[0] ?? '',
|
|
56
|
+
end_date: activeCycle.endsAt?.toISOString().split('T')[0] ?? ''
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Check if cycle changed and update config with history
|
|
60
|
+
const existingData = await loadCycleData();
|
|
61
|
+
const oldCycleId = config.current_cycle?.id ?? existingData?.cycleId;
|
|
62
|
+
|
|
63
|
+
if (oldCycleId && oldCycleId !== cycleId) {
|
|
64
|
+
const oldCycleName = config.current_cycle?.name ?? existingData?.cycleName ?? 'Unknown';
|
|
65
|
+
console.log(`Cycle changed: ${oldCycleName} → ${cycleName}`);
|
|
66
|
+
|
|
67
|
+
// Move old cycle to history
|
|
68
|
+
if (config.current_cycle) {
|
|
69
|
+
config.cycle_history = config.cycle_history ?? [];
|
|
70
|
+
config.cycle_history.unshift(config.current_cycle);
|
|
71
|
+
// Keep only last 10 cycles
|
|
72
|
+
if (config.cycle_history.length > 10) {
|
|
73
|
+
config.cycle_history = config.cycle_history.slice(0, 10);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Update current cycle
|
|
78
|
+
config.current_cycle = newCycleInfo;
|
|
60
79
|
await saveConfig(config);
|
|
61
|
-
console.log('Config updated with new cycle.');
|
|
80
|
+
console.log('Config updated with new cycle (old cycle saved to history).');
|
|
62
81
|
} else {
|
|
63
|
-
|
|
82
|
+
// Update current cycle info even if ID unchanged (dates might change)
|
|
83
|
+
if (!config.current_cycle || config.current_cycle.id !== cycleId) {
|
|
84
|
+
config.current_cycle = newCycleInfo;
|
|
85
|
+
await saveConfig(config);
|
|
86
|
+
}
|
|
87
|
+
console.log(`Current cycle: ${cycleName}`);
|
|
64
88
|
}
|
|
65
89
|
|
|
66
|
-
const cycleId = config.current_cycle.id;
|
|
67
|
-
|
|
68
90
|
// Phase 2: Fetch workflow states
|
|
69
91
|
const workflowStates = await client.workflowStates({
|
|
70
92
|
filter: { team: { id: { eq: teamId } } }
|
|
@@ -72,8 +94,7 @@ Examples:
|
|
|
72
94
|
const stateMap = new Map(workflowStates.nodes.map(s => [s.name, s.id]));
|
|
73
95
|
const testingStateId = stateMap.get('Testing');
|
|
74
96
|
|
|
75
|
-
// Phase 3:
|
|
76
|
-
const existingData = await loadCycleData();
|
|
97
|
+
// Phase 3: Build existing tasks map for preserving local status
|
|
77
98
|
const existingTasksMap = new Map(existingData?.tasks.map(t => [t.id, t]));
|
|
78
99
|
|
|
79
100
|
// Phase 4: Fetch current issues with full content
|
|
@@ -168,22 +189,22 @@ Examples:
|
|
|
168
189
|
tasks.push(task);
|
|
169
190
|
}
|
|
170
191
|
|
|
171
|
-
// Sort by priority
|
|
192
|
+
// Sort by priority using config order
|
|
172
193
|
tasks.sort((a, b) => {
|
|
173
|
-
const pa = a.priority
|
|
174
|
-
const pb = b.priority
|
|
194
|
+
const pa = getPrioritySortIndex(a.priority, config.priority_order);
|
|
195
|
+
const pb = getPrioritySortIndex(b.priority, config.priority_order);
|
|
175
196
|
return pa - pb;
|
|
176
197
|
});
|
|
177
198
|
|
|
178
199
|
const newData: CycleData = {
|
|
179
200
|
cycleId: cycleId,
|
|
180
|
-
cycleName:
|
|
201
|
+
cycleName: cycleName,
|
|
181
202
|
updatedAt: new Date().toISOString(),
|
|
182
203
|
tasks: tasks
|
|
183
204
|
};
|
|
184
205
|
|
|
185
206
|
await saveCycleData(newData);
|
|
186
|
-
console.log(`\n✅ Synced ${tasks.length} tasks for ${
|
|
207
|
+
console.log(`\n✅ Synced ${tasks.length} tasks for ${cycleName}.`);
|
|
187
208
|
if (updatedCount > 0) {
|
|
188
209
|
console.log(` Updated ${updatedCount} issues to Testing in Linear.`);
|
|
189
210
|
}
|
package/scripts/utils.ts
CHANGED
|
@@ -52,14 +52,41 @@ export interface LabelConfig {
|
|
|
52
52
|
color?: string;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
export interface CycleInfo {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
start_date: string;
|
|
59
|
+
end_date: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
export interface Config {
|
|
56
63
|
teams: Record<string, TeamConfig>;
|
|
57
64
|
users: Record<string, UserConfig>;
|
|
58
|
-
labels
|
|
59
|
-
priorities
|
|
60
|
-
statuses
|
|
61
|
-
status_transitions
|
|
62
|
-
|
|
65
|
+
labels?: Record<string, LabelConfig>;
|
|
66
|
+
priorities?: Record<string, { value: number; name: string }>;
|
|
67
|
+
statuses?: Record<string, { name: string; type: string }>;
|
|
68
|
+
status_transitions?: Record<string, string>;
|
|
69
|
+
priority_order?: string[]; // e.g., ['urgent', 'high', 'medium', 'low', 'none']
|
|
70
|
+
current_cycle?: CycleInfo;
|
|
71
|
+
cycle_history?: CycleInfo[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Linear priority value to name mapping (fixed by Linear API)
|
|
75
|
+
export const PRIORITY_NAMES: Record<number, string> = {
|
|
76
|
+
0: 'none',
|
|
77
|
+
1: 'urgent',
|
|
78
|
+
2: 'high',
|
|
79
|
+
3: 'medium',
|
|
80
|
+
4: 'low'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const DEFAULT_PRIORITY_ORDER = ['urgent', 'high', 'medium', 'low', 'none'];
|
|
84
|
+
|
|
85
|
+
export function getPrioritySortIndex(priority: number, priorityOrder?: string[]): number {
|
|
86
|
+
const order = priorityOrder ?? DEFAULT_PRIORITY_ORDER;
|
|
87
|
+
const name = PRIORITY_NAMES[priority] ?? 'none';
|
|
88
|
+
const index = order.indexOf(name);
|
|
89
|
+
return index === -1 ? order.length : index;
|
|
63
90
|
}
|
|
64
91
|
|
|
65
92
|
export interface Attachment {
|
package/scripts/work-on.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
|
-
import { getLinearClient, loadConfig, loadLocalConfig, loadCycleData, saveCycleData, getUserEmail, getTeamId } from './utils';
|
|
2
|
+
import { getLinearClient, loadConfig, loadLocalConfig, loadCycleData, saveCycleData, getUserEmail, getTeamId, getPrioritySortIndex } from './utils';
|
|
3
3
|
|
|
4
4
|
const PRIORITY_LABELS: Record<number, string> = {
|
|
5
|
-
0: '⚪',
|
|
5
|
+
0: '⚪ None',
|
|
6
6
|
1: '🔴 Urgent',
|
|
7
7
|
2: '🟠 High',
|
|
8
8
|
3: '🟡 Medium',
|
|
@@ -53,8 +53,8 @@ Examples:
|
|
|
53
53
|
!excludedEmails.has(t.assignee ?? '')
|
|
54
54
|
)
|
|
55
55
|
.sort((a, b) => {
|
|
56
|
-
const pa = a.priority
|
|
57
|
-
const pb = b.priority
|
|
56
|
+
const pa = getPrioritySortIndex(a.priority, config.priority_order);
|
|
57
|
+
const pb = getPrioritySortIndex(b.priority, config.priority_order);
|
|
58
58
|
return pa - pb;
|
|
59
59
|
});
|
|
60
60
|
|