buddy-workbench 0.1.27 → 0.1.29

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": "buddy-workbench",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,6 +24,15 @@
24
24
  "version": "node -e \"const v=process.env.npm_package_version; const fs=require('fs'); ['ui/package.json', 'ui/package-lock.json'].forEach(p=>{if(fs.existsSync(p)){const j=JSON.parse(fs.readFileSync(p)); j.version=v; if(j.packages&&j.packages['']){j.packages[''].version=v;} fs.writeFileSync(p, JSON.stringify(j,null,2)+'\\n');}});\" && git add ui/package.json ui/package-lock.json"
25
25
  },
26
26
  "devbuddyChangelog": [
27
+ {
28
+ "version": "0.1.28",
29
+ "name": "Presentation planning and presenter tools",
30
+ "notes": [
31
+ "Added a Presentations library and Presentation Studio for planning, timing, and delivering presentations.",
32
+ "Added a configurable audience view with Markdown takeaways, themes, time boxes, branding, and full-screen support.",
33
+ "Added step-linked quick captures and Markdown export with clipboard copy and .md downloads."
34
+ ]
35
+ },
27
36
  {
28
37
  "version": "0.1.27",
29
38
  "name": "Data backups and settings refinements",
@@ -16,8 +16,8 @@ router.post('/scan', async (req, res, next) => {
16
16
  const result = await scanFolder({
17
17
  sourcePath,
18
18
  targetPath,
19
- includeSubfolders: Boolean(includeSubfolders),
20
- skipOrganized: skipOrganized !== false
19
+ includeSubfolders: includeSubfolders === true || includeSubfolders === 'true',
20
+ skipOrganized: skipOrganized !== false && skipOrganized !== 'false'
21
21
  });
22
22
 
23
23
  res.json(result);
@@ -112,6 +112,10 @@ router.get('/:id/issues', async (req, res) => {
112
112
  return res.status(404).json({ error: 'Filter not found.' });
113
113
  }
114
114
 
115
+ if (filter.mockIssues && Array.isArray(filter.mockIssues)) {
116
+ return res.json({ filter, issues: filter.mockIssues });
117
+ }
118
+
115
119
  const jiraHost = getJiraHost();
116
120
  if (!jiraHost) {
117
121
  return res.status(400).json({ error: 'Jira domain is not configured. Please set Domain in Settings.' });
@@ -121,7 +125,7 @@ router.get('/:id/issues', async (req, res) => {
121
125
  const token = settings.jiraAccessToken;
122
126
 
123
127
  try {
124
- const url = `https://${jiraHost}/rest/api/2/search?jql=filter%3D${encodeURIComponent(filter.filterId)}&maxResults=200&fields=summary,priority,duedate,status`;
128
+ const url = `https://${jiraHost}/rest/api/2/search?jql=filter%3D${encodeURIComponent(filter.filterId)}&maxResults=200&fields=summary,priority,duedate,status,issuetype`;
125
129
  const headers = {};
126
130
  if (token) {
127
131
  headers.Authorization = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
@@ -144,6 +148,7 @@ router.get('/:id/issues', async (req, res) => {
144
148
  priority: fields.priority || null,
145
149
  dueDate: fields.duedate || null,
146
150
  status: fields.status?.name || null,
151
+ issueType: fields.issuetype?.name || null,
147
152
  url: `https://${jiraHost}/browse/${key}`
148
153
  };
149
154
  });
@@ -153,14 +158,14 @@ router.get('/:id/issues', async (req, res) => {
153
158
 
154
159
  res.json({ filter, issues });
155
160
  } catch (error) {
156
- const targetUrl = `https://${jiraHost}/rest/api/2/search?jql=filter%3D${encodeURIComponent(filter.filterId)}&maxResults=200&fields=summary,priority,duedate,status`;
161
+ const targetUrl = `https://${jiraHost}/rest/api/2/search?jql=filter%3D${encodeURIComponent(filter.filterId)}&maxResults=200&fields=summary,priority,duedate,status,issuetype`;
157
162
  res.status(500).json({ error: error.message || 'Failed to fetch Jira issues.', targetUrl });
158
163
  }
159
164
  });
160
165
 
161
166
  // Clone an issue
162
167
  router.post('/issues/clone', async (req, res) => {
163
- const { issueKey, summary } = req.body || {};
168
+ const { issueKey, summary, issueType } = req.body || {};
164
169
  if (!summary || typeof summary !== 'string' || !summary.trim()) {
165
170
  return res.status(400).json({ error: 'Summary is required for clone.' });
166
171
  }
@@ -194,20 +199,78 @@ router.post('/issues/clone', async (req, res) => {
194
199
  // Ignore if fetching current user fails
195
200
  }
196
201
 
197
- const projectKey = issueKey ? issueKey.split('-')[0] : '';
202
+ // Fetch original issue details to preserve issue type, project, description, priority
203
+ let origIssueType = null;
204
+ let origProjectKey = null;
205
+ let origDescription = null;
206
+ let origPriority = null;
207
+
208
+ if (issueKey) {
209
+ try {
210
+ const issueUrl = `https://${jiraHost}/rest/api/2/issue/${encodeURIComponent(issueKey)}`;
211
+ const origRes = await httpClient.get(issueUrl, { headers });
212
+ if (origRes.status === 200 && origRes.data?.fields) {
213
+ const fields = origRes.data.fields;
214
+ if (fields.issuetype) {
215
+ if (fields.issuetype.id) {
216
+ origIssueType = { id: fields.issuetype.id };
217
+ } else if (fields.issuetype.name) {
218
+ origIssueType = { name: fields.issuetype.name };
219
+ }
220
+ }
221
+ if (fields.project?.key) {
222
+ origProjectKey = fields.project.key;
223
+ }
224
+ if (fields.description) {
225
+ origDescription = fields.description;
226
+ }
227
+ if (fields.priority) {
228
+ if (fields.priority.id) {
229
+ origPriority = { id: fields.priority.id };
230
+ } else if (fields.priority.name) {
231
+ origPriority = { name: fields.priority.name };
232
+ }
233
+ }
234
+ }
235
+ } catch {
236
+ // Ignore if fetching original issue fails
237
+ }
238
+ }
239
+
240
+ // Determine issue type fallback: fetched original issueType -> passed issueType -> Task
241
+ const targetIssueType = origIssueType || (issueType ? { name: issueType } : { name: 'Task' });
242
+ const projectKey = origProjectKey || (issueKey ? issueKey.split('-')[0] : '');
198
243
  const url = `https://${jiraHost}/rest/api/2/issue`;
199
244
  const payload = {
200
245
  fields: {
201
246
  summary: summary.trim(),
202
247
  ...(projectKey ? { project: { key: projectKey } } : {}),
203
- issuetype: { name: 'Task' },
204
- ...(assigneeField ? { assignee: assigneeField } : {})
248
+ issuetype: targetIssueType,
249
+ ...(assigneeField ? { assignee: assigneeField } : {}),
250
+ ...(origDescription ? { description: origDescription } : {}),
251
+ ...(origPriority ? { priority: origPriority } : {})
205
252
  }
206
253
  };
207
254
 
208
- const response = await httpClient.post(url, payload, { headers });
255
+ let response = await httpClient.post(url, payload, { headers });
256
+ // Fallback if optional fields like description or priority caused issue creation rejection
257
+ if (response.status !== 201 && response.status !== 200 && (origPriority || origDescription)) {
258
+ const fallbackPayload = {
259
+ fields: {
260
+ summary: summary.trim(),
261
+ ...(projectKey ? { project: { key: projectKey } } : {}),
262
+ issuetype: targetIssueType,
263
+ ...(assigneeField ? { assignee: assigneeField } : {})
264
+ }
265
+ };
266
+ response = await httpClient.post(url, fallbackPayload, { headers });
267
+ }
268
+
209
269
  if (response.status !== 201 && response.status !== 200) {
210
- const errMsg = response.data?.errorMessages?.[0] || response.data?.message || `Jira API returned status ${response.status}`;
270
+ const errMsg = response.data?.errorMessages?.[0] ||
271
+ (response.data?.errors ? Object.values(response.data.errors).join(', ') : null) ||
272
+ response.data?.message ||
273
+ `Jira API returned status ${response.status}`;
211
274
  return res.status(response.status).json({ error: errMsg });
212
275
  }
213
276