@docupace/pipeline 1.0.39-newrelease.8 → 1.0.39
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/next/execute.sh +2 -8
- package/package.json +3 -11
- package/src/auth0/cli.js +3 -1
- package/src/auth0/configure.js +203 -9
- package/src/auth0/metadata.js +2 -1
- package/src/changeset/cli.js +232 -76
- package/src/changeset/validate.js +1 -38
- package/src/jenkins/setParams.groovy +2 -1
- package/src/next/apache.js +35 -38
- package/src/next/cli.js +5 -14
- package/src/next/config.js +8 -18
- package/src/next/deploy.js +3 -7
- package/src/next/haproxy.js +19 -9
- package/src/next/jira.js +7 -55
- package/src/next/publish.js +30 -53
- package/src/next/vault.js +5 -7
- package/terraform/apply.sh +1 -1
- package/terraform/init.sh +2 -1
- package/terraform/plan.sh +1 -1
- package/README.md +0 -136
- package/src/changeset/canny.js +0 -116
- package/src/changeset/changelog-module.cjs +0 -26
- package/src/changeset/changelog.js +0 -134
- package/src/changeset/config.js +0 -11
- package/src/changeset/create.js +0 -171
- package/src/changeset/openai.js +0 -437
- package/src/changeset/package-util.js +0 -86
- package/src/changeset/util.js +0 -43
- package/src/changeset/version.js +0 -102
- package/src/index.mjs +0 -2
- package/src/next/logger.js +0 -17
- package/src/next/util.js +0 -93
package/src/changeset/openai.js
DELETED
|
@@ -1,437 +0,0 @@
|
|
|
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
|
-
/**
|
|
48
|
-
* Log token usage information
|
|
49
|
-
* @param {string} ticketId - The JIRA ticket ID
|
|
50
|
-
* @param {Object} tokenUsage - Token usage data
|
|
51
|
-
*/
|
|
52
|
-
/**
|
|
53
|
-
* Strip sensitive or unnecessary fields from a JIRA ticket object
|
|
54
|
-
* @param {Object} ticketData - The JIRA ticket data to clean
|
|
55
|
-
* @returns {Object} - The cleaned JIRA ticket data
|
|
56
|
-
*/
|
|
57
|
-
function stripJiraTicketFields(ticketData) {
|
|
58
|
-
if (!ticketData || !ticketData.fields) return ticketData;
|
|
59
|
-
|
|
60
|
-
const fieldsToStrip = [
|
|
61
|
-
'customfield_10020',
|
|
62
|
-
'worklog',
|
|
63
|
-
'creator',
|
|
64
|
-
'reporter',
|
|
65
|
-
'assignee',
|
|
66
|
-
'customfield_10095',
|
|
67
|
-
'customfield_10000',
|
|
68
|
-
'votes',
|
|
69
|
-
'watches',
|
|
70
|
-
'customfield_10083',
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
const cleanedData = JSON.parse(JSON.stringify(ticketData)); // Create a deep copy
|
|
74
|
-
|
|
75
|
-
// Strip specified fields
|
|
76
|
-
fieldsToStrip.forEach((field) => {
|
|
77
|
-
if (cleanedData.fields[field]) {
|
|
78
|
-
delete cleanedData.fields[field];
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Remove all null fields
|
|
83
|
-
Object.keys(cleanedData.fields).forEach((field) => {
|
|
84
|
-
if (cleanedData.fields[field] === null) {
|
|
85
|
-
delete cleanedData.fields[field];
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Clean up comments if they exist
|
|
90
|
-
if (cleanedData.fields.comment && cleanedData.fields.comment.comments) {
|
|
91
|
-
cleanedData.fields.comment.comments =
|
|
92
|
-
cleanedData.fields.comment.comments.map((comment) => {
|
|
93
|
-
const cleanComment = { ...comment };
|
|
94
|
-
if (cleanComment.author) delete cleanComment.author;
|
|
95
|
-
if (cleanComment.updateAuthor) delete cleanComment.updateAuthor;
|
|
96
|
-
|
|
97
|
-
// Remove null fields from comments
|
|
98
|
-
Object.keys(cleanComment).forEach((key) => {
|
|
99
|
-
if (cleanComment[key] === null) {
|
|
100
|
-
delete cleanComment[key];
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return cleanComment;
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return cleanedData;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Fetch JIRA ticket information
|
|
113
|
-
* @param {string} ticketId - The JIRA ticket ID
|
|
114
|
-
* @returns {Promise<Object>} - The JIRA ticket data
|
|
115
|
-
*/
|
|
116
|
-
async function fetchJiraTicket(ticketId) {
|
|
117
|
-
if (!JIRA_API_KEY || !JIRA_API_EMAIL) {
|
|
118
|
-
console.warn('JIRA credentials not provided. Skipping JIRA API call.');
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const auth = Buffer.from(`${JIRA_API_EMAIL}:${JIRA_API_KEY}`).toString(
|
|
123
|
-
'base64',
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const response = await fetch(
|
|
128
|
-
`${JIRA_BASE_URL}/rest/api/3/issue/${ticketId}`,
|
|
129
|
-
{
|
|
130
|
-
headers: {
|
|
131
|
-
Authorization: `Basic ${auth}`,
|
|
132
|
-
'Content-Type': 'application/json',
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
if (!response.ok) {
|
|
138
|
-
console.error(
|
|
139
|
-
`Error fetching JIRA ticket ${ticketId}: ${response.statusText}`,
|
|
140
|
-
);
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const data = await response.json();
|
|
145
|
-
const cleanedData = stripJiraTicketFields(data);
|
|
146
|
-
|
|
147
|
-
// Check if the ticket is a subtask
|
|
148
|
-
const isSubtask = cleanedData.fields.issuetype?.subtask || false;
|
|
149
|
-
let mainTicketId, subTaskTicketId, mainTicketData, subTaskTicketData;
|
|
150
|
-
|
|
151
|
-
// If it's a subtask, fetch the parent ticket as well
|
|
152
|
-
if (isSubtask && cleanedData.fields.parent) {
|
|
153
|
-
const parentTicketId = cleanedData.fields.parent.key;
|
|
154
|
-
mainTicketId = parentTicketId;
|
|
155
|
-
subTaskTicketId = ticketId;
|
|
156
|
-
delete cleanedData.fields.parent;
|
|
157
|
-
|
|
158
|
-
console.log(
|
|
159
|
-
`Fetching parent ticket ${parentTicketId} for subtask ${ticketId}`,
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
const parentResponse = await fetch(
|
|
163
|
-
`${JIRA_BASE_URL}/rest/api/3/issue/${parentTicketId}`,
|
|
164
|
-
{
|
|
165
|
-
headers: {
|
|
166
|
-
Authorization: `Basic ${auth}`,
|
|
167
|
-
'Content-Type': 'application/json',
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
if (parentResponse.ok) {
|
|
173
|
-
const parentData = await parentResponse.json();
|
|
174
|
-
const cleanedParentData = stripJiraTicketFields(parentData);
|
|
175
|
-
|
|
176
|
-
mainTicketData = cleanedParentData;
|
|
177
|
-
subTaskTicketData = cleanedData;
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
mainTicketId = ticketId;
|
|
181
|
-
mainTicketData = cleanedData;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
mainTicketData,
|
|
186
|
-
subTaskTicketData,
|
|
187
|
-
mainTicketId,
|
|
188
|
-
subTaskTicketId,
|
|
189
|
-
};
|
|
190
|
-
} catch (error) {
|
|
191
|
-
console.error(`Error fetching JIRA ticket ${ticketId}:`, error.message);
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Generate enhanced release notes using OpenAI
|
|
198
|
-
* @param {Object} ticketData - The JIRA ticket data
|
|
199
|
-
* @returns {Promise<Object>} - Enhanced release notes
|
|
200
|
-
*/
|
|
201
|
-
export async function generateEnhancedReleaseNotes(ticketData) {
|
|
202
|
-
if (!OPENAI_API_KEY) {
|
|
203
|
-
console.warn(
|
|
204
|
-
'OpenAI API key not provided. Skipping release notes enhancement.',
|
|
205
|
-
);
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (!ticketData) return null;
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
const systemPrompt = `Process the provided JSON from a Jira ticket to generate structured release notes in JSON format.
|
|
213
|
-
Follow this strict schema to generate your response:
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
"title": "A concise title for the release note (3-8 words)",
|
|
217
|
-
"summary": "A brief user-facing summary of changes (max 2 sentences)",
|
|
218
|
-
"technicalDescription": "Developer-friendly description of technical changes in markdown format (max 5 sentences)",
|
|
219
|
-
"userFacingDescription": "Provide only if changes made to user interface. Plain-language description of changes from a user perspective in markdown format (max 5 sentences)",
|
|
220
|
-
"technicalOnly": boolean (true/false whether this change is only technical with no user-visible impact),
|
|
221
|
-
"changeType": "New" | "Improved" | "Fixed",
|
|
222
|
-
"changeSize": "Small" | "Medium" | "Large"
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
When creating your response:
|
|
226
|
-
- Title: Create a concise title summarizing the change (3–8 words). Avoid repeating the Jira ticket title.
|
|
227
|
-
- Summary: Write a maximum two-sentence summary in user-facing language.
|
|
228
|
-
- 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
|
|
229
|
-
- 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.
|
|
230
|
-
- 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
|
|
231
|
-
- Change Type: Classify as “New”, “Improved”, or “Fixed”.
|
|
232
|
-
- Change Size: Estimate the impact as “Small”, “Medium”, or “Large”.
|
|
233
|
-
|
|
234
|
-
For Descriptions:
|
|
235
|
-
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.
|
|
236
|
-
Avoid restating the ticket title. Focus on what changed, how it works, and where it applies.
|
|
237
|
-
`;
|
|
238
|
-
|
|
239
|
-
const userPrompt = JSON.stringify(ticketData, null, 2);
|
|
240
|
-
const ticketId =
|
|
241
|
-
ticketData.mainTicketId || ticketData.mainTicketData?.key || 'unknown';
|
|
242
|
-
|
|
243
|
-
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
244
|
-
method: 'POST',
|
|
245
|
-
headers: {
|
|
246
|
-
Authorization: `Bearer ${OPENAI_API_KEY}`,
|
|
247
|
-
'Content-Type': 'application/json',
|
|
248
|
-
},
|
|
249
|
-
body: JSON.stringify({
|
|
250
|
-
model: OPENAI_MODEL,
|
|
251
|
-
messages: [
|
|
252
|
-
{ role: 'system', content: systemPrompt },
|
|
253
|
-
{ role: 'user', content: userPrompt },
|
|
254
|
-
],
|
|
255
|
-
response_format: {
|
|
256
|
-
type: 'json_schema',
|
|
257
|
-
json_schema: {
|
|
258
|
-
name: 'release_notes',
|
|
259
|
-
schema: {
|
|
260
|
-
type: 'object',
|
|
261
|
-
properties: {
|
|
262
|
-
title: {
|
|
263
|
-
type: 'string',
|
|
264
|
-
description:
|
|
265
|
-
'A concise title for the release note (3-8 words)',
|
|
266
|
-
},
|
|
267
|
-
summary: {
|
|
268
|
-
type: 'string',
|
|
269
|
-
description:
|
|
270
|
-
'A brief user-facing summary of changes (max 2 sentences)',
|
|
271
|
-
},
|
|
272
|
-
technicalDescription: {
|
|
273
|
-
type: 'string',
|
|
274
|
-
description:
|
|
275
|
-
'Developer-friendly description of technical changes (max 5 sentences)',
|
|
276
|
-
},
|
|
277
|
-
userFacingDescription: {
|
|
278
|
-
type: 'string',
|
|
279
|
-
description:
|
|
280
|
-
'Plain-language description of changes from a user perspective (max 5 sentences)',
|
|
281
|
-
},
|
|
282
|
-
technicalOnly: {
|
|
283
|
-
type: 'boolean',
|
|
284
|
-
description:
|
|
285
|
-
'Whether this change is technical-only with no user-visible impact',
|
|
286
|
-
},
|
|
287
|
-
changeSize: {
|
|
288
|
-
type: 'string',
|
|
289
|
-
enum: ['Small', 'Medium', 'Large'],
|
|
290
|
-
description: 'Size/scope of the change',
|
|
291
|
-
},
|
|
292
|
-
changeType: {
|
|
293
|
-
type: 'string',
|
|
294
|
-
enum: ['New', 'Improved', 'Fixed'],
|
|
295
|
-
description: 'Type of change',
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
required: [
|
|
299
|
-
'title',
|
|
300
|
-
'summary',
|
|
301
|
-
'technicalOnly',
|
|
302
|
-
'changeSize',
|
|
303
|
-
'changeType',
|
|
304
|
-
],
|
|
305
|
-
additionalProperties: false,
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
temperature: 0, // Lower temperature for more structured/predictable responses
|
|
310
|
-
}),
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
if (!response.ok) {
|
|
314
|
-
console.error(`Error from OpenAI API: ${response.statusText}`);
|
|
315
|
-
return null;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const data = await response.json();
|
|
319
|
-
|
|
320
|
-
const content = JSON.parse(data.choices[0].message.content);
|
|
321
|
-
|
|
322
|
-
// Validate response using Zod schema
|
|
323
|
-
const validatedResponse = openAiResponseSchema.parse(content);
|
|
324
|
-
|
|
325
|
-
return validatedResponse;
|
|
326
|
-
} catch (error) {
|
|
327
|
-
console.error('Error generating enhanced release notes:', error.message);
|
|
328
|
-
return null;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
export async function writeYamlFile(mainTicketId, enhancedNotes) {
|
|
333
|
-
// Create changeset directory if it doesn't exist
|
|
334
|
-
if (!fs.existsSync(path.join(process.cwd(), '.release-notes'))) {
|
|
335
|
-
fs.mkdirSync(path.join(process.cwd(), '.release-notes'));
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const yamlFileName = `${mainTicketId}.yaml`;
|
|
339
|
-
const yamlFilePath = path.join(CHANGESET_DIR, yamlFileName);
|
|
340
|
-
|
|
341
|
-
const yamlContent = yaml.dump(
|
|
342
|
-
{
|
|
343
|
-
ticketId: mainTicketId,
|
|
344
|
-
title: enhancedNotes.title,
|
|
345
|
-
summary: enhancedNotes.summary,
|
|
346
|
-
technicalDescription: {
|
|
347
|
-
__line: enhancedNotes.technicalDescription,
|
|
348
|
-
},
|
|
349
|
-
userFacingDescription: {
|
|
350
|
-
__line: enhancedNotes.userFacingDescription,
|
|
351
|
-
},
|
|
352
|
-
technicalOnly: enhancedNotes.technicalOnly,
|
|
353
|
-
changeSize: enhancedNotes.changeSize,
|
|
354
|
-
changeType: enhancedNotes.changeType,
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
lineWidth: -1, // Don't wrap lines
|
|
358
|
-
noRefs: true,
|
|
359
|
-
schema: yaml.DEFAULT_SCHEMA.extend({
|
|
360
|
-
implicit: [
|
|
361
|
-
new yaml.Type('tag:yaml.org,2002:str', {
|
|
362
|
-
kind: 'scalar',
|
|
363
|
-
instanceOf: Object,
|
|
364
|
-
resolve: function (object) {
|
|
365
|
-
return object !== null && object.hasOwnProperty('__line');
|
|
366
|
-
},
|
|
367
|
-
construct: function (object) {
|
|
368
|
-
return object.__line;
|
|
369
|
-
},
|
|
370
|
-
predicate: function (object) {
|
|
371
|
-
return object.hasOwnProperty('__line');
|
|
372
|
-
},
|
|
373
|
-
represent: function (object) {
|
|
374
|
-
return { __line: object.__line };
|
|
375
|
-
},
|
|
376
|
-
}),
|
|
377
|
-
],
|
|
378
|
-
}),
|
|
379
|
-
},
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
// Replace the problematic YAML output with properly formatted literal block scalars
|
|
383
|
-
const fixedYaml = yamlContent
|
|
384
|
-
.replace(/technicalDescription: '?(.*?)'?$/m, (match, p1) => {
|
|
385
|
-
return `technicalDescription: |\n ${enhancedNotes.technicalDescription.replace(/\n/g, '\n ')}`;
|
|
386
|
-
})
|
|
387
|
-
.replace(/userFacingDescription: '?(.*?)'?$/m, (match, p1) => {
|
|
388
|
-
return `userFacingDescription: |\n ${enhancedNotes.userFacingDescription.replace(/\n/g, '\n ')}`;
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
fs.writeFileSync(yamlFilePath, fixedYaml, 'utf8');
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export async function getReleaseNotes(ticketId) {
|
|
395
|
-
const ticketData = await fetchJiraTicket(ticketId);
|
|
396
|
-
|
|
397
|
-
if (ticketData) {
|
|
398
|
-
// Generate enhanced release notes
|
|
399
|
-
const { mainTicketData, subTaskTicketData, mainTicketId } = ticketData;
|
|
400
|
-
|
|
401
|
-
const yamlFileName = `${mainTicketId}.yaml`;
|
|
402
|
-
const yamlFilePath = path.join(CHANGESET_DIR, yamlFileName);
|
|
403
|
-
|
|
404
|
-
if (fs.existsSync(yamlFilePath)) {
|
|
405
|
-
console.log(
|
|
406
|
-
`Notes for ticket ${mainTicketId} already exist, loading from file`,
|
|
407
|
-
);
|
|
408
|
-
try {
|
|
409
|
-
const fileContent = fs.readFileSync(yamlFilePath, 'utf8');
|
|
410
|
-
const enhancedNotes = yaml.load(fileContent);
|
|
411
|
-
return {
|
|
412
|
-
yamlFilePath,
|
|
413
|
-
enhancedNotes,
|
|
414
|
-
mainTicketId: enhancedNotes.ticketId,
|
|
415
|
-
};
|
|
416
|
-
} catch (error) {
|
|
417
|
-
console.error(
|
|
418
|
-
`Error loading YAML file for ${mainTicketId}:`,
|
|
419
|
-
error.message,
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const enhancedNotes = await generateEnhancedReleaseNotes({
|
|
425
|
-
mainTicketData,
|
|
426
|
-
subTaskTicketData,
|
|
427
|
-
mainTicketId,
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
if (enhancedNotes) {
|
|
431
|
-
await writeYamlFile(mainTicketId, enhancedNotes);
|
|
432
|
-
return { yamlFilePath, enhancedNotes, mainTicketId };
|
|
433
|
-
} else {
|
|
434
|
-
throw new Error(`Cannot generate release notes for ${mainTicketId}`);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
export function getPackageNames(rootPath = '.') {
|
|
5
|
-
const appsDir = path.join(rootPath, 'apps');
|
|
6
|
-
const packagesDir = path.join(rootPath, 'packages');
|
|
7
|
-
const rootPackageJson = path.join(rootPath, 'package.json');
|
|
8
|
-
|
|
9
|
-
// Check if this is a monorepo (has apps and/or packages directories)
|
|
10
|
-
const hasAppsDir =
|
|
11
|
-
fs.existsSync(appsDir) && fs.statSync(appsDir).isDirectory();
|
|
12
|
-
const hasPackagesDir =
|
|
13
|
-
fs.existsSync(packagesDir) && fs.statSync(packagesDir).isDirectory();
|
|
14
|
-
|
|
15
|
-
if (hasAppsDir || hasPackagesDir) {
|
|
16
|
-
// This is a monorepo
|
|
17
|
-
const packages = [];
|
|
18
|
-
|
|
19
|
-
// Get packages from apps directory
|
|
20
|
-
if (hasAppsDir) {
|
|
21
|
-
const appsPackages = getPackagesFromDirectory(appsDir);
|
|
22
|
-
packages.push(...appsPackages);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Get packages from packages directory
|
|
26
|
-
if (hasPackagesDir) {
|
|
27
|
-
const packagesPackages = getPackagesFromDirectory(packagesDir);
|
|
28
|
-
packages.push(...packagesPackages);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return packages;
|
|
32
|
-
} else {
|
|
33
|
-
// This is not a monorepo, return single package
|
|
34
|
-
if (fs.existsSync(rootPackageJson)) {
|
|
35
|
-
try {
|
|
36
|
-
const packageJson = JSON.parse(
|
|
37
|
-
fs.readFileSync(rootPackageJson, 'utf8'),
|
|
38
|
-
);
|
|
39
|
-
return packageJson.name
|
|
40
|
-
? [{ name: packageJson.name, path: rootPath }]
|
|
41
|
-
: [];
|
|
42
|
-
} catch (error) {
|
|
43
|
-
console.error('Error reading root package.json:', error);
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function getPackagesFromDirectory(dirPath) {
|
|
52
|
-
const packages = [];
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const items = fs.readdirSync(dirPath);
|
|
56
|
-
|
|
57
|
-
for (const item of items) {
|
|
58
|
-
const itemPath = path.join(dirPath, item);
|
|
59
|
-
const packageJsonPath = path.join(itemPath, 'package.json');
|
|
60
|
-
|
|
61
|
-
// Check if item is a directory and has package.json
|
|
62
|
-
if (
|
|
63
|
-
fs.statSync(itemPath).isDirectory() &&
|
|
64
|
-
fs.existsSync(packageJsonPath)
|
|
65
|
-
) {
|
|
66
|
-
try {
|
|
67
|
-
const packageJson = JSON.parse(
|
|
68
|
-
fs.readFileSync(packageJsonPath, 'utf8'),
|
|
69
|
-
);
|
|
70
|
-
if (packageJson.name) {
|
|
71
|
-
packages.push({
|
|
72
|
-
name: packageJson.name,
|
|
73
|
-
path: itemPath,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error(`Error reading package.json in ${itemPath}:`, error);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.error(`Error reading directory ${dirPath}:`, error);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return packages;
|
|
86
|
-
}
|
package/src/changeset/util.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
async function fileExists(filePath) {
|
|
5
|
-
try {
|
|
6
|
-
await fs.promises.access(filePath);
|
|
7
|
-
return true;
|
|
8
|
-
} catch {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export async function getMainPackage() {
|
|
13
|
-
const mainModulePath = process.cwd();
|
|
14
|
-
return getPackage(mainModulePath);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function getPackage(mainModulePath) {
|
|
18
|
-
if (mainModulePath) {
|
|
19
|
-
const packageJsonPath = path.join(mainModulePath, 'package.json');
|
|
20
|
-
if (await fileExists(packageJsonPath)) {
|
|
21
|
-
const fileContent = await fs.promises.readFile(packageJsonPath, 'utf-8');
|
|
22
|
-
const packageJson = JSON.parse(fileContent);
|
|
23
|
-
return packageJson;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
throw new Error('Main package.json not found');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Locates and extracts a JIRA Ticket ID from a given string.
|
|
31
|
-
* <p>
|
|
32
|
-
* The JIRA Ticket ID is expected to follow the pattern of uppercase letters (at least two) followed by a hyphen and digits (e.g., DC-1234, INFP-9).
|
|
33
|
-
* @param str the string to search for a JIRA ticket ID
|
|
34
|
-
* @returns {JIRA-TicketID|null}
|
|
35
|
-
*/
|
|
36
|
-
export function findJiraTicketId(str) {
|
|
37
|
-
if (!str) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
const regex = /([A-Z]{2,}-\d+)/;
|
|
41
|
-
const matches = str.match(regex);
|
|
42
|
-
return matches ? matches[0] : null;
|
|
43
|
-
}
|
package/src/changeset/version.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { setGitInfo } from '../next/util.js';
|
|
2
|
-
import { $ } from 'zx';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import { updateComponentVersion } from '../next/jira.js';
|
|
5
|
-
import { findJiraTicketId } from './util.js';
|
|
6
|
-
|
|
7
|
-
const changeSetPackage = `{
|
|
8
|
-
"name": "changeset-package",
|
|
9
|
-
"version": "1.0.0",
|
|
10
|
-
"description": "",
|
|
11
|
-
"scripts": {
|
|
12
|
-
},
|
|
13
|
-
"type": "module",
|
|
14
|
-
"keywords": [],
|
|
15
|
-
"author": "",
|
|
16
|
-
"license": "ISC",
|
|
17
|
-
"dependencies": {},
|
|
18
|
-
"devDependencies": {
|
|
19
|
-
"@changesets/cli": "^2.27.10",
|
|
20
|
-
"@docupace/pipeline": "1.0.39-newrelease.7"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
`;
|
|
24
|
-
|
|
25
|
-
export async function loadChangesetOutput() {
|
|
26
|
-
const outputPath = './changeset-output.json';
|
|
27
|
-
const jsonOutput = JSON.parse(await fs.promises.readFile(outputPath, 'utf8'));
|
|
28
|
-
return jsonOutput;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function removeChangesetOutput() {
|
|
32
|
-
const outputPath = './changeset-output.json';
|
|
33
|
-
await fs.promises.rm(outputPath);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function versionStatus(sinceBranch) {
|
|
37
|
-
const outputPath = './changeset-output.json';
|
|
38
|
-
|
|
39
|
-
if (sinceBranch) {
|
|
40
|
-
await $`pnpm changeset status --output=${outputPath} --since=${sinceBranch}`;
|
|
41
|
-
} else {
|
|
42
|
-
await $`pnpm changeset status --output=${outputPath}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Read the file and parse JSON
|
|
46
|
-
const jsonOutput = JSON.parse(await fs.promises.readFile(outputPath, 'utf8'));
|
|
47
|
-
|
|
48
|
-
return jsonOutput.releases;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export async function versionCommand(closeVersion = false) {
|
|
52
|
-
console.log('📦 Running Changeset versioning...');
|
|
53
|
-
await setGitInfo();
|
|
54
|
-
await $`mv package.json package.json.bak`;
|
|
55
|
-
await $`mv pnpm-lock.yaml pnpm-lock.yaml.bak`;
|
|
56
|
-
await fs.promises.writeFile('package.json', changeSetPackage);
|
|
57
|
-
await $`pnpm install`;
|
|
58
|
-
await $`rm package.json pnpm-lock.yaml`;
|
|
59
|
-
await $`mv package.json.bak package.json`;
|
|
60
|
-
await $`mv pnpm-lock.yaml.bak pnpm-lock.yaml`;
|
|
61
|
-
if (closeVersion) {
|
|
62
|
-
await $`pnpm changeset pre exit`;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const releases = await versionStatus();
|
|
67
|
-
|
|
68
|
-
await $`pnpm changeset version`;
|
|
69
|
-
|
|
70
|
-
await removeChangesetOutput();
|
|
71
|
-
|
|
72
|
-
return releases;
|
|
73
|
-
} catch {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function updateJiraWithVersion(releases) {
|
|
79
|
-
const lastCommit = (await $`git log -1 --pretty=%B`).stdout.trim();
|
|
80
|
-
console.log(`Last commit '${lastCommit}' being processed to get Jira ticket`);
|
|
81
|
-
|
|
82
|
-
const jiraTicketId = findJiraTicketId(lastCommit);
|
|
83
|
-
|
|
84
|
-
// If we have all the necessary data, update the component version in Jira
|
|
85
|
-
if (jiraTicketId) {
|
|
86
|
-
for (const release of releases) {
|
|
87
|
-
try {
|
|
88
|
-
await updateComponentVersion(
|
|
89
|
-
jiraTicketId,
|
|
90
|
-
release.name,
|
|
91
|
-
release.newVersion,
|
|
92
|
-
);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.error(
|
|
95
|
-
`Failed to update Jira component version: ${error.message}`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
console.log('✅ Changeset versioning complete.');
|
|
102
|
-
}
|
package/src/index.mjs
DELETED
package/src/next/logger.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { $ as original$ } from 'zx';
|
|
2
|
-
|
|
3
|
-
// Wrapped version of $
|
|
4
|
-
export const $ = async (strings, ...values) => {
|
|
5
|
-
const command = strings.reduce(
|
|
6
|
-
(acc, str, i) => acc + str + (values[i] ?? ''),
|
|
7
|
-
'',
|
|
8
|
-
);
|
|
9
|
-
console.log(`Executing: ${command}`);
|
|
10
|
-
try {
|
|
11
|
-
const result = await original$(strings, ...values);
|
|
12
|
-
return result;
|
|
13
|
-
} catch (error) {
|
|
14
|
-
console.error(`Error executing: ${command}`, error);
|
|
15
|
-
throw error;
|
|
16
|
-
}
|
|
17
|
-
};
|