@docupace/pipeline 1.0.86 → 1.0.87

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.
@@ -0,0 +1,567 @@
1
+ #!/usr/bin/env node
2
+ import dotenv from 'dotenv';
3
+ dotenv.config({ path: ['.env.pipeline.local', 'config/.env.pipeline.local'] });
4
+
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import fetch from 'node-fetch';
8
+ import yaml from 'js-yaml';
9
+ import { z } from 'zod';
10
+
11
+ // Configuration
12
+ export const JIRA_BASE_URL =
13
+ process.env.JIRA_BASE_URL || 'https://docupacellc.atlassian.net';
14
+
15
+ const JIRA_API_KEY = process.env.JIRA_API_KEY;
16
+ const JIRA_API_EMAIL = process.env.JIRA_API_EMAIL;
17
+ const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
18
+ const OPENAI_MODEL = process.env.OPENAI_MODEL || 'gpt-4.1';
19
+
20
+ const CHANGESET_DIR = path.join(process.cwd(), '.release-notes');
21
+
22
+ // Define the Zod schema for OpenAI response
23
+ const openAiResponseSchema = z.object({
24
+ title: z
25
+ .string()
26
+ .describe('A concise title for the release note (3-8 words)'),
27
+ summary: z.string().describe('A brief user-facing summary of changes'),
28
+ technicalDescription: z
29
+ .string()
30
+ .optional()
31
+ .describe('Developer-friendly description of technical changes'),
32
+ userFacingDescription: z
33
+ .string()
34
+ .optional()
35
+ .describe('Plain-language description of changes from a user perspective'),
36
+ technicalOnly: z
37
+ .boolean()
38
+ .describe(
39
+ 'Whether this change is technical-only with no user-visible impact',
40
+ ),
41
+ changeSize: z
42
+ .enum(['Small', 'Medium', 'Large'])
43
+ .describe('Size/scope of the change'),
44
+ changeType: z.enum(['New', 'Improved', 'Fixed']).describe('Type of change'),
45
+ });
46
+
47
+ // Define the Zod schema for changelog summary
48
+ const changeLogSummarySchema = z.object({
49
+ summary: z
50
+ .string()
51
+ .describe(
52
+ 'A concise summary of what is being implemented in this ticket or subtask',
53
+ ),
54
+ });
55
+
56
+ /**
57
+ * Log token usage information
58
+ * @param {string} ticketId - The JIRA ticket ID
59
+ * @param {Object} tokenUsage - Token usage data
60
+ */
61
+ /**
62
+ * Strip sensitive or unnecessary fields from a JIRA ticket object
63
+ * @param {Object} ticketData - The JIRA ticket data to clean
64
+ * @returns {Object} - The cleaned JIRA ticket data
65
+ */
66
+ function stripJiraTicketFields(ticketData) {
67
+ if (!ticketData || !ticketData.fields) return ticketData;
68
+
69
+ const fieldsToStrip = [
70
+ 'customfield_10020',
71
+ 'worklog',
72
+ 'creator',
73
+ 'reporter',
74
+ 'assignee',
75
+ 'customfield_10095',
76
+ 'customfield_10000',
77
+ 'votes',
78
+ 'watches',
79
+ 'customfield_10083',
80
+ ];
81
+
82
+ const cleanedData = JSON.parse(JSON.stringify(ticketData)); // Create a deep copy
83
+
84
+ // Strip specified fields
85
+ fieldsToStrip.forEach((field) => {
86
+ if (cleanedData.fields[field]) {
87
+ delete cleanedData.fields[field];
88
+ }
89
+ });
90
+
91
+ // Remove all null fields
92
+ Object.keys(cleanedData.fields).forEach((field) => {
93
+ if (cleanedData.fields[field] === null) {
94
+ delete cleanedData.fields[field];
95
+ }
96
+ });
97
+
98
+ // Clean up comments if they exist
99
+ if (cleanedData.fields.comment && cleanedData.fields.comment.comments) {
100
+ cleanedData.fields.comment.comments =
101
+ cleanedData.fields.comment.comments.map((comment) => {
102
+ const cleanComment = { ...comment };
103
+ if (cleanComment.author) delete cleanComment.author;
104
+ if (cleanComment.updateAuthor) delete cleanComment.updateAuthor;
105
+
106
+ // Remove null fields from comments
107
+ Object.keys(cleanComment).forEach((key) => {
108
+ if (cleanComment[key] === null) {
109
+ delete cleanComment[key];
110
+ }
111
+ });
112
+
113
+ return cleanComment;
114
+ });
115
+ }
116
+
117
+ return cleanedData;
118
+ }
119
+
120
+ /**
121
+ * Fetch JIRA ticket information
122
+ * @param {string} ticketId - The JIRA ticket ID
123
+ * @returns {Promise<Object>} - The JIRA ticket data
124
+ */
125
+ async function fetchJiraTicket(ticketId) {
126
+ if (!JIRA_API_KEY || !JIRA_API_EMAIL) {
127
+ console.warn('JIRA credentials not provided. Skipping JIRA API call.');
128
+ return null;
129
+ }
130
+
131
+ const auth = Buffer.from(`${JIRA_API_EMAIL}:${JIRA_API_KEY}`).toString(
132
+ 'base64',
133
+ );
134
+
135
+ try {
136
+ const response = await fetch(
137
+ `${JIRA_BASE_URL}/rest/api/3/issue/${ticketId}`,
138
+ {
139
+ headers: {
140
+ Authorization: `Basic ${auth}`,
141
+ 'Content-Type': 'application/json',
142
+ },
143
+ },
144
+ );
145
+
146
+ if (!response.ok) {
147
+ console.error(
148
+ `Error fetching JIRA ticket ${ticketId}: ${response.statusText}`,
149
+ );
150
+ return null;
151
+ }
152
+
153
+ const data = await response.json();
154
+ const cleanedData = stripJiraTicketFields(data);
155
+
156
+ // Check if the ticket is a subtask
157
+ const isSubtask = cleanedData.fields.issuetype?.subtask || false;
158
+ let mainTicketId, subTaskTicketId, mainTicketData, subTaskTicketData;
159
+
160
+ // If it's a subtask, fetch the parent ticket as well
161
+ if (isSubtask && cleanedData.fields.parent) {
162
+ const parentTicketId = cleanedData.fields.parent.key;
163
+ mainTicketId = parentTicketId;
164
+ subTaskTicketId = ticketId;
165
+ delete cleanedData.fields.parent;
166
+
167
+ console.log(
168
+ `Fetching parent ticket ${parentTicketId} for subtask ${ticketId}`,
169
+ );
170
+
171
+ const parentResponse = await fetch(
172
+ `${JIRA_BASE_URL}/rest/api/3/issue/${parentTicketId}`,
173
+ {
174
+ headers: {
175
+ Authorization: `Basic ${auth}`,
176
+ 'Content-Type': 'application/json',
177
+ },
178
+ },
179
+ );
180
+
181
+ if (parentResponse.ok) {
182
+ const parentData = await parentResponse.json();
183
+ const cleanedParentData = stripJiraTicketFields(parentData);
184
+
185
+ mainTicketData = cleanedParentData;
186
+ subTaskTicketData = cleanedData;
187
+ }
188
+ } else {
189
+ mainTicketId = ticketId;
190
+ mainTicketData = cleanedData;
191
+ }
192
+
193
+ return {
194
+ mainTicketData,
195
+ subTaskTicketData,
196
+ mainTicketId,
197
+ subTaskTicketId,
198
+ };
199
+ } catch (error) {
200
+ console.error(`Error fetching JIRA ticket ${ticketId}:`, error.message);
201
+ return null;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Generate a changelog summary using OpenAI
207
+ * @param {Object} params - Parameters object
208
+ * @param {Object} params.taskTicketData - The task/subtask ticket data
209
+ * @param {Object} params.mainTicketData - The main ticket data
210
+ * @returns {Promise<Object>} - Changelog summary
211
+ */
212
+ export async function generateChangeLog({ taskTicketData, mainTicketData }) {
213
+ if (!OPENAI_API_KEY) {
214
+ console.warn('OpenAI API key not provided. Skipping changelog generation.');
215
+ return null;
216
+ }
217
+
218
+ if (!taskTicketData || !mainTicketData) return null;
219
+
220
+ try {
221
+ // Determine if this is a subtask by comparing ticket keys
222
+ const isSubtask = taskTicketData.key !== mainTicketData.key;
223
+
224
+ let systemPrompt;
225
+ let userPrompt;
226
+
227
+ if (isSubtask) {
228
+ // For subtasks, provide context from the main ticket
229
+ systemPrompt = `You are analyzing a Jira subtask and its parent ticket. Generate a concise summary of what is being implemented in this subtask.
230
+
231
+ The summary should:
232
+ - Focus on what the subtask is implementing specifically
233
+ - Include context from the parent ticket to provide understanding
234
+ - Be 1-2 sentences maximum
235
+ - Be clear and user-facing (avoid overly technical jargon)
236
+
237
+ Return your response in this JSON format:
238
+ {
239
+ "summary": "A concise summary of what is being implemented in the subtask"
240
+ }`;
241
+
242
+ userPrompt = JSON.stringify(
243
+ {
244
+ mainTicket: mainTicketData,
245
+ subtask: taskTicketData,
246
+ },
247
+ null,
248
+ 2,
249
+ );
250
+ } else {
251
+ // For regular tickets, just summarize the main ticket
252
+ systemPrompt = `You are analyzing a Jira ticket. Generate a concise summary of what is being implemented.
253
+
254
+ The summary should:
255
+ - Be 1-2 sentences maximum
256
+ - Be clear and user-facing (avoid overly technical jargon)
257
+ - Capture the main purpose of the ticket
258
+
259
+ Return your response in this JSON format:
260
+ {
261
+ "summary": "A concise summary of what is being implemented"
262
+ }`;
263
+
264
+ userPrompt = JSON.stringify(mainTicketData, null, 2);
265
+ }
266
+
267
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
268
+ method: 'POST',
269
+ headers: {
270
+ Authorization: `Bearer ${OPENAI_API_KEY}`,
271
+ 'Content-Type': 'application/json',
272
+ },
273
+ body: JSON.stringify({
274
+ model: OPENAI_MODEL,
275
+ messages: [
276
+ { role: 'system', content: systemPrompt },
277
+ { role: 'user', content: userPrompt },
278
+ ],
279
+ response_format: {
280
+ type: 'json_schema',
281
+ json_schema: {
282
+ name: 'changelog_summary',
283
+ schema: {
284
+ type: 'object',
285
+ properties: {
286
+ summary: {
287
+ type: 'string',
288
+ description: 'A concise summary of what is being implemented',
289
+ },
290
+ },
291
+ required: ['summary'],
292
+ additionalProperties: false,
293
+ },
294
+ },
295
+ },
296
+ temperature: 0,
297
+ }),
298
+ });
299
+
300
+ if (!response.ok) {
301
+ console.error(`Error from OpenAI API: ${response.statusText}`);
302
+ return null;
303
+ }
304
+
305
+ const data = await response.json();
306
+ const content = JSON.parse(data.choices[0].message.content);
307
+
308
+ // Validate response using Zod schema
309
+ const validatedResponse = changeLogSummarySchema.parse(content);
310
+
311
+ return validatedResponse;
312
+ } catch (error) {
313
+ console.error('Error generating changelog summary:', error.message);
314
+ return null;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Generate enhanced release notes using OpenAI
320
+ * @param {Object} ticketData - The JIRA ticket data
321
+ * @returns {Promise<Object>} - Enhanced release notes
322
+ */
323
+
324
+ export async function generateEnhancedReleaseNotes(ticketData) {
325
+ if (!OPENAI_API_KEY) {
326
+ console.warn(
327
+ 'OpenAI API key not provided. Skipping release notes enhancement.',
328
+ );
329
+ return null;
330
+ }
331
+
332
+ if (!ticketData) return null;
333
+
334
+ try {
335
+ const systemPrompt = `Process the provided JSON from a Jira ticket to generate structured release notes in JSON format.
336
+ Follow this strict schema to generate your response:
337
+
338
+ {
339
+ "title": "A concise title for the release note (3-8 words)",
340
+ "summary": "A brief user-facing summary of changes (max 2 sentences)",
341
+ "technicalDescription": "Developer-friendly description of technical changes in markdown format (max 5 sentences)",
342
+ "userFacingDescription": "Provide only if changes made to user interface. Plain-language description of changes from a user perspective in markdown format (max 5 sentences)",
343
+ "technicalOnly": boolean (true/false whether this change is only technical with no user-visible impact),
344
+ "changeType": "New" | "Improved" | "Fixed",
345
+ "changeSize": "Small" | "Medium" | "Large"
346
+ }
347
+
348
+ When creating your response:
349
+ - Title: Create a concise title summarizing the change (3–8 words). Avoid repeating the Jira ticket title.
350
+ - Summary: Write a maximum two-sentence summary in user-facing language.
351
+ - Technical Description: Use developer-friendly language to explain what changed at the system or code level. Include only if specified: API impacts, architectural changes, or deployment considerations. Format using markdown. Use backticks for code (\`like_this\`), bold for important terms, and line breaks for readability. Avoid headings like Technical Changes
352
+ - User-Facing Description: Provide only if changes are made to the user interface. Use plain language to explain what users will see or benefit from. Include affected UI elements, workflows, or systems. Prefer bullet points if listing multiple effects or changes. Use markdown formatting for clarity.
353
+ - Technical Only: Set to true if the change has no visible effect to end users. Examples for technical only are API changes, refactoring of code or architectual changes that do not have changes to the user interface
354
+ - Change Type: Classify as “New”, “Improved”, or “Fixed”.
355
+ - Change Size: Estimate the impact as “Small”, “Medium”, or “Large”.
356
+
357
+ For Descriptions:
358
+ Avoid large blocks of text. Break content into short paragraphs or lists. Use markdown consistently: code, bold, italic, and single level bullet lists (- Item). Make your response easy to scan, especially the descriptions. Focus on what changed, how it works, and where it applies.
359
+ Avoid restating the ticket title. Focus on what changed, how it works, and where it applies.
360
+ `;
361
+
362
+ const userPrompt = JSON.stringify(ticketData, null, 2);
363
+
364
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
365
+ method: 'POST',
366
+ headers: {
367
+ Authorization: `Bearer ${OPENAI_API_KEY}`,
368
+ 'Content-Type': 'application/json',
369
+ },
370
+ body: JSON.stringify({
371
+ model: OPENAI_MODEL,
372
+ messages: [
373
+ { role: 'system', content: systemPrompt },
374
+ { role: 'user', content: userPrompt },
375
+ ],
376
+ response_format: {
377
+ type: 'json_schema',
378
+ json_schema: {
379
+ name: 'release_notes',
380
+ schema: {
381
+ type: 'object',
382
+ properties: {
383
+ title: {
384
+ type: 'string',
385
+ description:
386
+ 'A concise title for the release note (3-8 words)',
387
+ },
388
+ summary: {
389
+ type: 'string',
390
+ description:
391
+ 'A brief user-facing summary of changes (max 2 sentences)',
392
+ },
393
+ technicalDescription: {
394
+ type: 'string',
395
+ description:
396
+ 'Developer-friendly description of technical changes (max 5 sentences)',
397
+ },
398
+ userFacingDescription: {
399
+ type: 'string',
400
+ description:
401
+ 'Plain-language description of changes from a user perspective (max 5 sentences)',
402
+ },
403
+ technicalOnly: {
404
+ type: 'boolean',
405
+ description:
406
+ 'Whether this change is technical-only with no user-visible impact',
407
+ },
408
+ changeSize: {
409
+ type: 'string',
410
+ enum: ['Small', 'Medium', 'Large'],
411
+ description: 'Size/scope of the change',
412
+ },
413
+ changeType: {
414
+ type: 'string',
415
+ enum: ['New', 'Improved', 'Fixed'],
416
+ description: 'Type of change',
417
+ },
418
+ },
419
+ required: [
420
+ 'title',
421
+ 'summary',
422
+ 'technicalOnly',
423
+ 'changeSize',
424
+ 'changeType',
425
+ ],
426
+ additionalProperties: false,
427
+ },
428
+ },
429
+ },
430
+ temperature: 0, // Lower temperature for more structured/predictable responses
431
+ }),
432
+ });
433
+
434
+ if (!response.ok) {
435
+ console.error(`Error from OpenAI API: ${response.statusText}`);
436
+ return null;
437
+ }
438
+
439
+ const data = await response.json();
440
+
441
+ const content = JSON.parse(data.choices[0].message.content);
442
+
443
+ // Validate response using Zod schema
444
+ const validatedResponse = openAiResponseSchema.parse(content);
445
+
446
+ return validatedResponse;
447
+ } catch (error) {
448
+ console.error('Error generating enhanced release notes:', error.message);
449
+ return null;
450
+ }
451
+ }
452
+
453
+ export async function writeYamlFile(mainTicketId, enhancedNotes) {
454
+ // Create changeset directory if it doesn't exist
455
+ if (!fs.existsSync(path.join(process.cwd(), '.release-notes'))) {
456
+ fs.mkdirSync(path.join(process.cwd(), '.release-notes'));
457
+ }
458
+
459
+ const yamlFileName = `${mainTicketId}.yaml`;
460
+ const yamlFilePath = path.join(CHANGESET_DIR, yamlFileName);
461
+
462
+ const yamlContent = yaml.dump(
463
+ {
464
+ ticketId: mainTicketId,
465
+ title: enhancedNotes.title,
466
+ summary: enhancedNotes.summary,
467
+ technicalDescription: {
468
+ __line: enhancedNotes.technicalDescription,
469
+ },
470
+ userFacingDescription: {
471
+ __line: enhancedNotes.userFacingDescription,
472
+ },
473
+ technicalOnly: enhancedNotes.technicalOnly,
474
+ changeSize: enhancedNotes.changeSize,
475
+ changeType: enhancedNotes.changeType,
476
+ },
477
+ {
478
+ lineWidth: -1, // Don't wrap lines
479
+ noRefs: true,
480
+ schema: yaml.DEFAULT_SCHEMA.extend({
481
+ implicit: [
482
+ new yaml.Type('tag:yaml.org,2002:str', {
483
+ kind: 'scalar',
484
+ instanceOf: Object,
485
+ resolve: function (object) {
486
+ return object !== null && object['__line'];
487
+ },
488
+ construct: function (object) {
489
+ return object.__line;
490
+ },
491
+ represent: function (object) {
492
+ return { __line: object.__line };
493
+ },
494
+ }),
495
+ ],
496
+ }),
497
+ },
498
+ );
499
+
500
+ // Replace the problematic YAML output with properly formatted literal block scalars
501
+ const fixedYaml = yamlContent
502
+ .replace(/technicalDescription: '?(.*?)'?$/m, () => {
503
+ return `technicalDescription: |\n ${enhancedNotes.technicalDescription.replace(/\n/g, '\n ')}`;
504
+ })
505
+ .replace(/userFacingDescription: '?(.*?)'?$/m, () => {
506
+ return `userFacingDescription: |\n ${enhancedNotes.userFacingDescription.replace(/\n/g, '\n ')}`;
507
+ });
508
+
509
+ fs.writeFileSync(yamlFilePath, fixedYaml, 'utf8');
510
+ }
511
+
512
+ export async function getReleaseNotes(ticketId) {
513
+ const ticketData = await fetchJiraTicket(ticketId);
514
+
515
+ if (ticketData) {
516
+ // Generate enhanced release notes
517
+ const { mainTicketData, subTaskTicketData, mainTicketId } = ticketData;
518
+
519
+ const yamlFileName = `${mainTicketId}.yaml`;
520
+ const yamlFilePath = path.join(CHANGESET_DIR, yamlFileName);
521
+
522
+ if (fs.existsSync(yamlFilePath)) {
523
+ console.log(
524
+ `Notes for ticket ${mainTicketId} already exist, loading from file`,
525
+ );
526
+ try {
527
+ const fileContent = fs.readFileSync(yamlFilePath, 'utf8');
528
+ const enhancedNotes = yaml.load(fileContent);
529
+ const changeLog = await generateChangeLog({
530
+ taskTicketData: subTaskTicketData ?? mainTicketData,
531
+ mainTicketData,
532
+ });
533
+ return {
534
+ yamlFilePath,
535
+ enhancedNotes,
536
+ mainTicketId: enhancedNotes.ticketId,
537
+ summary: changeLog.summary,
538
+ };
539
+ } catch (error) {
540
+ console.error(
541
+ `Error loading YAML file for ${mainTicketId}:`,
542
+ error.message,
543
+ );
544
+ }
545
+ } else {
546
+ const [changeLog, enhancedNotes] = await Promise.all([
547
+ generateChangeLog({
548
+ taskTicketData: subTaskTicketData ?? mainTicketData,
549
+ mainTicketData,
550
+ }),
551
+ generateEnhancedReleaseNotes(mainTicketData),
552
+ ]);
553
+
554
+ if (enhancedNotes) {
555
+ await writeYamlFile(mainTicketId, enhancedNotes);
556
+ return {
557
+ yamlFilePath,
558
+ enhancedNotes,
559
+ mainTicketId,
560
+ summary: changeLog.summary,
561
+ };
562
+ } else {
563
+ throw new Error(`Cannot generate release notes for ${mainTicketId}`);
564
+ }
565
+ }
566
+ }
567
+ }
@@ -2,20 +2,9 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { $ } from 'zx';
4
4
 
5
- import { isMonoRepo, setGitInfo } from '../next/util.js';
5
+ import { setGitInfo } from '../next/util.js';
6
6
  import { versionStatus } from './version.js';
7
7
 
8
- const CHANGESET_DIR = '.changeset';
9
-
10
- function areArraysEqualIgnoringOrder(arr1, arr2) {
11
- if (arr1.length !== arr2.length) return false;
12
-
13
- const sortedA = [...arr1].sort();
14
- const sortedB = [...arr2].sort();
15
-
16
- return sortedA.every((element, index) => element === sortedB[index]);
17
- }
18
-
19
8
  export async function validateChangesets() {
20
9
  await setGitInfo();
21
10
  const destBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
@@ -1,10 +1,19 @@
1
1
  import { setGitInfo } from '../next/util.js';
2
2
  import { $ } from 'zx';
3
- import fs from 'fs';
3
+ import fs, { readFileSync } from 'fs';
4
4
  import { updateComponentVersion } from '../next/jira.js';
5
5
  import { findJiraTicketId } from './util.js';
6
6
  import { revertBaseBranch, updateBaseBranch } from './changeset-config.js';
7
7
  import semver from 'semver';
8
+ import { dirname, join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ const getPackageVersion = () => {
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const packageJsonPath = join(__dirname, '../../package.json');
14
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
15
+ return packageJson.version;
16
+ };
8
17
 
9
18
  const changeSetPackage = `{
10
19
  "name": "changeset-package",
@@ -18,11 +27,23 @@ const changeSetPackage = `{
18
27
  "license": "ISC",
19
28
  "dependencies": {},
20
29
  "devDependencies": {
21
- "@changesets/cli": "^2.27.10"
30
+ "@changesets/cli": "^2.27.10",
31
+ "@docupace/pipeline": "${getPackageVersion()}"
22
32
  }
23
33
  }
24
34
  `;
25
35
 
36
+ export async function loadChangesetOutput() {
37
+ const outputPath = './changeset-output.json';
38
+ const jsonOutput = JSON.parse(await fs.promises.readFile(outputPath, 'utf8'));
39
+ return jsonOutput;
40
+ }
41
+
42
+ async function removeChangesetOutput() {
43
+ const outputPath = './changeset-output.json';
44
+ await fs.promises.rm(outputPath);
45
+ }
46
+
26
47
  export function toShortVersion(version) {
27
48
  const parsed = semver.parse(version);
28
49
  if (!parsed) return null;
@@ -41,7 +62,6 @@ export async function versionStatus(sinceBranch) {
41
62
  // Read the file and parse JSON
42
63
  const jsonOutput = JSON.parse(await fs.promises.readFile(outputPath, 'utf8'));
43
64
 
44
- await fs.promises.rm(outputPath);
45
65
  return jsonOutput.releases;
46
66
  }
47
67
 
@@ -68,6 +88,8 @@ export async function versionCommand(closeVersion = false) {
68
88
  revertBaseBranch(originalBaseBranch);
69
89
  await $`pnpm changeset version`;
70
90
 
91
+ await removeChangesetOutput();
92
+
71
93
  return releases;
72
94
  } catch {
73
95
  revertBaseBranch(originalBaseBranch);
package/src/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import changelogCanny from './changeset/changelog.js';
2
+ export default changelogCanny;
@@ -2,12 +2,8 @@
2
2
 
3
3
  import fs from 'fs';
4
4
  import { execSync } from 'child_process';
5
- import path from 'path';
6
- import { fileURLToPath } from 'url';
7
5
 
8
6
  // Get __dirname equivalent in ESM
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
7
 
12
8
  /**
13
9
  * Default merge logic function - can be overridden
@@ -16,7 +12,7 @@ const __dirname = path.dirname(__filename);
16
12
  * @returns {string} - The merged content
17
13
  * @throws {Error} - If the conflict cannot be automatically merged
18
14
  */
19
- export function defaultMergeLogic(headContent, branchContent) {
15
+ export function defaultMergeLogic() {
20
16
  // Default implementation - throws error
21
17
  // Users should provide their own implementation
22
18
  throw new Error(
@@ -78,7 +78,7 @@ function readMergeConfig(verbose = false) {
78
78
  * Extract repository name from git URL
79
79
  */
80
80
  function getRepoName(gitUrl) {
81
- const match = gitUrl.match(/\/([^\/]+)\.git$/);
81
+ const match = gitUrl.match(/\/([^/]+)\.git$/);
82
82
  return match ? match[1] : gitUrl.split('/').pop().replace('.git', '');
83
83
  }
84
84
 
@@ -459,7 +459,6 @@ async function mergeAll(verbose = false) {
459
459
  console.log(`═══════════════════════════════════════`);
460
460
 
461
461
  allResults.forEach((result, index) => {
462
- const { from, to } = result.mergeBranch;
463
462
  const status = result.success ? '✅' : '⚠️';
464
463
  console.log(`\n${status} Merge branch set ${index + 1}:`);
465
464