@docupace/pipeline 1.0.39-matttest.23 → 1.0.39-matttest.24
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 +4 -2
- package/src/changeset/cli.js +60 -220
- package/src/changeset/create.js +182 -0
- package/src/changeset/util.js +27 -0
- package/src/changeset/validate.js +11 -25
- package/src/changeset/version.js +89 -0
- package/src/next/jira.js +1 -6
- package/src/next/publish.js +4 -3
- package/src/next/util.js +27 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docupace/pipeline",
|
|
3
|
-
"version": "1.0.39-matttest.
|
|
3
|
+
"version": "1.0.39-matttest.24",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"commander": "^12.1.0",
|
|
24
24
|
"dotenv": "^16.4.7",
|
|
25
25
|
"fs-extra": "^11.3.0",
|
|
26
|
+
"inquirer": "^12.6.1",
|
|
26
27
|
"js-yaml": "^4.1.0",
|
|
27
28
|
"lodash": "^4.17.21",
|
|
28
29
|
"openai": "^4.76.3",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"test-deploy-core": "dp-next-cli deploy-core-pipeline dev template",
|
|
43
44
|
"test-deploy-from-site": "dp-next-cli deploy-from-site --from-env dev --to-env dev --from-site financialdev --to-site template --deploy-core true",
|
|
44
45
|
"test-run-pipline": "dp-next-cli run-pipeline -r docupace-next-e2e -b master -p run-tests-qa",
|
|
45
|
-
"test-validate": "dp-changset-cli validate"
|
|
46
|
+
"test-validate": "dp-changset-cli validate",
|
|
47
|
+
"test-create": "dp-changset-cli create-changeset"
|
|
46
48
|
}
|
|
47
49
|
}
|
package/src/changeset/cli.js
CHANGED
|
@@ -1,146 +1,72 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { $ } from 'zx';
|
|
4
|
-
import path from 'path';
|
|
5
4
|
import { validateChangesets } from './validate.js';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import OpenAI from 'openai';
|
|
8
|
-
import { zodResponseFormat } from 'openai/helpers/zod';
|
|
9
|
-
import z from 'zod';
|
|
10
5
|
import dotenv from 'dotenv';
|
|
11
|
-
import {
|
|
6
|
+
import { getChangedAppPackages, isMonoRepo, setGitInfo } from '../next/util.js';
|
|
7
|
+
import { updateJiraWithVersion, versionCommand } from './version.js';
|
|
8
|
+
import { createChangeset } from './create.js';
|
|
9
|
+
import { getPackage } from './util.js';
|
|
10
|
+
|
|
12
11
|
dotenv.config({ path: ['.env.pipeline.local', 'config/.env.pipeline.local'] });
|
|
13
|
-
import { setGitInfo } from '../next/util.js';
|
|
14
12
|
|
|
15
13
|
const program = new Command();
|
|
16
14
|
|
|
17
15
|
const args = process.argv;
|
|
18
16
|
|
|
19
|
-
async function
|
|
20
|
-
|
|
21
|
-
// Use path.join to build a safe path to `.changeset`
|
|
22
|
-
const targetDir = path.join(process.cwd(), '.changeset');
|
|
17
|
+
async function commitTagCommand(releases) {
|
|
18
|
+
console.log('📌 Checking for updates to commit...');
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.
|
|
20
|
+
// Check if there are changes to commit
|
|
21
|
+
const status = (await $`git status --porcelain`).stdout.trim();
|
|
22
|
+
if (!status) {
|
|
23
|
+
console.log('✅ No changes to commit. Exiting.');
|
|
24
|
+
return;
|
|
28
25
|
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function run(command) {
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
exec(command, { shell: true }, (error, stdout, stderr) => {
|
|
34
|
-
if (error) {
|
|
35
|
-
reject(error);
|
|
36
|
-
} else {
|
|
37
|
-
resolve({ stdout, stderr });
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
26
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return true;
|
|
47
|
-
} catch {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
27
|
+
console.log('📌 Committing and tagging the new version...');
|
|
28
|
+
await setGitInfo();
|
|
29
|
+
await $`git add .`;
|
|
51
30
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (await fileExists(packageJsonPath)) {
|
|
57
|
-
const fileContent = await fs.promises.readFile(packageJsonPath, 'utf-8');
|
|
58
|
-
const packageJson = JSON.parse(fileContent);
|
|
59
|
-
return packageJson;
|
|
60
|
-
}
|
|
31
|
+
if (isMonoRepo()) {
|
|
32
|
+
await commitTagMono(releases);
|
|
33
|
+
} else {
|
|
34
|
+
await commitTag();
|
|
61
35
|
}
|
|
62
|
-
throw new Error('Main package.json not found');
|
|
63
36
|
}
|
|
64
37
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
"type": "module",
|
|
72
|
-
"keywords": [],
|
|
73
|
-
"author": "",
|
|
74
|
-
"license": "ISC",
|
|
75
|
-
"dependencies": {},
|
|
76
|
-
"devDependencies": {
|
|
77
|
-
"@changesets/cli": "^2.27.10"
|
|
78
|
-
}
|
|
38
|
+
export function getAppReleases(releases) {
|
|
39
|
+
const appReleases = releases.filter((release) =>
|
|
40
|
+
release.name.endsWith('-app'),
|
|
41
|
+
);
|
|
42
|
+
return appReleases;
|
|
79
43
|
}
|
|
80
|
-
`;
|
|
81
|
-
|
|
82
|
-
async function versionCommand(closeVersion = false) {
|
|
83
|
-
console.log('📦 Running Changeset versioning...');
|
|
84
|
-
await setGitInfo();
|
|
85
|
-
await $`mv package.json package.json.bak`;
|
|
86
|
-
await $`mv pnpm-lock.yaml pnpm-lock.yaml.bak`;
|
|
87
|
-
await fs.promises.writeFile('package.json', changeSetPackage);
|
|
88
|
-
await $`pnpm install`;
|
|
89
|
-
await $`rm package.json pnpm-lock.yaml`;
|
|
90
|
-
await $`mv package.json.bak package.json`;
|
|
91
|
-
await $`mv pnpm-lock.yaml.bak pnpm-lock.yaml`;
|
|
92
|
-
if (closeVersion) {
|
|
93
|
-
await $`pnpm changeset pre exit`;
|
|
94
|
-
}
|
|
95
44
|
|
|
96
|
-
|
|
45
|
+
function getAppNameFromPackageName(packageName) {
|
|
46
|
+
const appName = packageName.includes('/')
|
|
47
|
+
? packageName.split('/')[1]
|
|
48
|
+
: packageName;
|
|
97
49
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
await fs.promises.readFile('package.json', 'utf-8'),
|
|
101
|
-
);
|
|
102
|
-
const version = packageJson.version;
|
|
103
|
-
const packageName = packageJson.name;
|
|
50
|
+
return appName;
|
|
51
|
+
}
|
|
104
52
|
|
|
105
|
-
|
|
106
|
-
|
|
53
|
+
async function commitTagMono(releases) {
|
|
54
|
+
let versions = [];
|
|
55
|
+
for (const release of getAppReleases(releases)) {
|
|
56
|
+
const newVersion = release.newVersion;
|
|
57
|
+
const appName = getAppNameFromPackageName(release.name);
|
|
58
|
+
versions.push(`${appName}@${newVersion}`);
|
|
107
59
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const jiraTicketMatch = lastCommit.match(/([A-Z]+-\d+)/);
|
|
114
|
-
const jiraTicketId = jiraTicketMatch ? jiraTicketMatch[0] : null;
|
|
115
|
-
|
|
116
|
-
// If we have all the necessary data, update the component version in Jira
|
|
117
|
-
if (jiraTicketId && version && packageName) {
|
|
118
|
-
try {
|
|
119
|
-
await updateComponentVersion(jiraTicketId, packageName, version);
|
|
120
|
-
} catch (error) {
|
|
121
|
-
console.error(
|
|
122
|
-
`Failed to update Jira component version: ${error.message}`,
|
|
123
|
-
);
|
|
124
|
-
}
|
|
60
|
+
await $`git commit -m "Version bump to ${versions.join(',')} and changelog"`;
|
|
61
|
+
for (const newVersion of versions) {
|
|
62
|
+
await $`git tag v${newVersion}`;
|
|
63
|
+
console.log(`✅ Version ${newVersion} tagged.`);
|
|
125
64
|
}
|
|
126
|
-
|
|
127
|
-
console.log(
|
|
128
|
-
return { version, packageName };
|
|
65
|
+
await $`git push origin --tags`;
|
|
66
|
+
console.log(`✅ Version pushed.`);
|
|
129
67
|
}
|
|
130
68
|
|
|
131
|
-
async function
|
|
132
|
-
console.log('📌 Checking for updates to commit...');
|
|
133
|
-
|
|
134
|
-
// Check if there are changes to commit
|
|
135
|
-
const status = (await $`git status --porcelain`).stdout.trim();
|
|
136
|
-
if (!status) {
|
|
137
|
-
console.log('✅ No changes to commit. Exiting.');
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
console.log('📌 Committing and tagging the new version...');
|
|
142
|
-
await setGitInfo();
|
|
143
|
-
await $`git add .`;
|
|
69
|
+
async function commitTag() {
|
|
144
70
|
const newVersion = (
|
|
145
71
|
await $`node -p "require('./package.json').version"`
|
|
146
72
|
).stdout.trim();
|
|
@@ -163,7 +89,7 @@ program
|
|
|
163
89
|
try {
|
|
164
90
|
await validateChangesets();
|
|
165
91
|
console.log('✅ Changeset validation passed.');
|
|
166
|
-
} catch
|
|
92
|
+
} catch {
|
|
167
93
|
console.error(
|
|
168
94
|
'❌ No changeset found. Please add a changeset to this PR. Please see more information at https://docupacellc.atlassian.net/wiki/spaces/PM/pages/1350893571/Creating+changesets+for+Docupace+Next',
|
|
169
95
|
);
|
|
@@ -192,8 +118,9 @@ program
|
|
|
192
118
|
.description('Run versioning and commit-and-tag in sequence')
|
|
193
119
|
.action(async () => {
|
|
194
120
|
console.log('📦 Running all steps...');
|
|
195
|
-
await versionCommand();
|
|
196
|
-
await
|
|
121
|
+
const releases = await versionCommand();
|
|
122
|
+
await updateJiraWithVersion(releases);
|
|
123
|
+
await commitTagCommand(releases);
|
|
197
124
|
});
|
|
198
125
|
|
|
199
126
|
program
|
|
@@ -201,15 +128,21 @@ program
|
|
|
201
128
|
.description('Closes a version')
|
|
202
129
|
.action(async () => {
|
|
203
130
|
console.log('📦 Running all steps...');
|
|
204
|
-
const
|
|
205
|
-
await commitTagCommand();
|
|
131
|
+
const releases = await versionCommand(true);
|
|
132
|
+
await commitTagCommand(releases);
|
|
206
133
|
|
|
207
|
-
// Get current branch
|
|
208
134
|
const { stdout: currentBranch } = await $`git rev-parse --abbrev-ref HEAD`;
|
|
209
135
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
136
|
+
if (isMonoRepo()) {
|
|
137
|
+
for (const release of getAppReleases(releases)) {
|
|
138
|
+
const appName = getAppNameFromPackageName(release.name);
|
|
139
|
+
await $`git checkout -b release/${appName}/${release.newVersion}`;
|
|
140
|
+
await $`git push origin release/${appName}/${release.newVersion}`;
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
await $`git checkout -b release/${releases[0].newVersion}`;
|
|
144
|
+
await $`git push origin release/${releases[0].newVersion}`;
|
|
145
|
+
}
|
|
213
146
|
|
|
214
147
|
// Return to original branch
|
|
215
148
|
await $`git checkout ${currentBranch.trim()}`;
|
|
@@ -227,100 +160,7 @@ program
|
|
|
227
160
|
'Creates a changeset based on the current branch and Jira ticket information',
|
|
228
161
|
)
|
|
229
162
|
.action(async () => {
|
|
230
|
-
|
|
231
|
-
const componentName = mainPackage.name;
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
// Step 1: Get the current branch name
|
|
235
|
-
const branchName = (await $`git branch --show-current`).stdout.trim();
|
|
236
|
-
console.log(`Current branch: ${branchName}`);
|
|
237
|
-
|
|
238
|
-
// Step 2: Extract Jira ticket number (e.g., matching JIRA-1234 pattern)
|
|
239
|
-
const jiraTicketMatch = branchName.match(/([A-Z]+-\d+)/);
|
|
240
|
-
if (!jiraTicketMatch) {
|
|
241
|
-
throw new Error('No Jira ticket found in the branch name.');
|
|
242
|
-
}
|
|
243
|
-
const jiraTicketId = jiraTicketMatch[0];
|
|
244
|
-
|
|
245
|
-
const jiraBaseUrl = 'https://docupacellc.atlassian.net';
|
|
246
|
-
const jiraEmail = process.env.JIRA_API_EMAIL;
|
|
247
|
-
const jiraApiKey = process.env.JIRA_API_KEY;
|
|
248
|
-
const jiraFullApiKey = `${jiraEmail}:${jiraApiKey}`;
|
|
249
|
-
const jiraUrl = `${jiraBaseUrl}/rest/api/2/issue/${jiraTicketId}`;
|
|
250
|
-
const jiraResponse = await fetch(jiraUrl, {
|
|
251
|
-
headers: {
|
|
252
|
-
Authorization: `Basic ${Buffer.from(jiraFullApiKey).toString('base64')}`,
|
|
253
|
-
'Content-Type': 'application/json',
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
if (!jiraResponse.ok) {
|
|
258
|
-
throw new Error(
|
|
259
|
-
`Failed to fetch Jira ticket. Status: ${jiraResponse.status}`,
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
const jiraTicketData = await jiraResponse.json();
|
|
263
|
-
|
|
264
|
-
const jiraSummary = jiraTicketData.fields.summary;
|
|
265
|
-
|
|
266
|
-
console.log(
|
|
267
|
-
`Generating release notes for ${jiraTicketId} - ${jiraSummary}`,
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
const openaiClient = new OpenAI({
|
|
271
|
-
apiKey: process.env['OPENAI_API_KEY'],
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
const ChangeRequestSchema = z.object({
|
|
275
|
-
change_type: z.enum(['patch', 'minor']),
|
|
276
|
-
summary: z.string(),
|
|
277
|
-
jira_ticket_id: z.string(),
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
const openAiResponse = await openaiClient.chat.completions.create({
|
|
281
|
-
model: 'gpt-4o-mini',
|
|
282
|
-
messages: [
|
|
283
|
-
{
|
|
284
|
-
role: 'system',
|
|
285
|
-
content: `Process JSON from a Jira ticket to generate simple maximum 2 sentence release note for the ticket and determine the change type for bug it should be a patch for new feature it should be a minor change`,
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
role: 'user',
|
|
289
|
-
content: JSON.stringify(jiraTicketData),
|
|
290
|
-
},
|
|
291
|
-
],
|
|
292
|
-
response_format: zodResponseFormat(
|
|
293
|
-
ChangeRequestSchema,
|
|
294
|
-
'changeset_schema',
|
|
295
|
-
),
|
|
296
|
-
temperature: 0,
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const assistantResponse = openAiResponse.choices[0].message.content;
|
|
300
|
-
const parsedResponse = JSON.parse(assistantResponse);
|
|
301
|
-
|
|
302
|
-
const { change_type, summary } = parsedResponse;
|
|
303
|
-
console.log(`Change type: ${change_type}`);
|
|
304
|
-
console.log(`Release note: ${summary}`);
|
|
305
|
-
|
|
306
|
-
// Step 5: Create a changeset .md file
|
|
307
|
-
const changesetContent = `---
|
|
308
|
-
"${componentName}": ${change_type}
|
|
309
|
-
---
|
|
310
|
-
|
|
311
|
-
${summary} [${jiraTicketId}]
|
|
312
|
-
`;
|
|
313
|
-
const changesetFile = `./.changeset/${jiraTicketId}.md`;
|
|
314
|
-
|
|
315
|
-
await createChangesetDirectory();
|
|
316
|
-
await fs.promises.writeFile(changesetFile, changesetContent);
|
|
317
|
-
console.log(`Created changeset file at ${changesetFile}`);
|
|
318
|
-
|
|
319
|
-
console.log('Done!');
|
|
320
|
-
} catch (err) {
|
|
321
|
-
console.error(`Error: ${err.message}`);
|
|
322
|
-
process.exit(1);
|
|
323
|
-
}
|
|
163
|
+
await createChangeset();
|
|
324
164
|
});
|
|
325
165
|
|
|
326
166
|
// Parse arguments
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { $ } from 'zx';
|
|
2
|
+
import OpenAI from 'openai';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
import { zodResponseFormat } from 'openai/helpers/zod';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { getAllAppPackages, isMonoRepo } from '../next/util.js';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import { getMainPackage, getPackage } from './util.js';
|
|
10
|
+
|
|
11
|
+
async function createChangesetDirectory() {
|
|
12
|
+
try {
|
|
13
|
+
// Use path.join to build a safe path to `.changeset`
|
|
14
|
+
const targetDir = path.join(process.cwd(), '.changeset');
|
|
15
|
+
|
|
16
|
+
// Create the directory recursively if it doesn't exist
|
|
17
|
+
await fs.promises.mkdir(targetDir, { recursive: true });
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error('Error creating .changeset directory:', error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gets the current git branch name
|
|
25
|
+
*/
|
|
26
|
+
async function getCurrentBranch() {
|
|
27
|
+
const branchName = (await $`git branch --show-current`).stdout.trim();
|
|
28
|
+
console.log(`Current branch: ${branchName}`);
|
|
29
|
+
return branchName;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extracts a Jira ticket ID from a branch name
|
|
34
|
+
*/
|
|
35
|
+
function extractJiraTicketId(branchName) {
|
|
36
|
+
const jiraTicketMatch = branchName.match(/([A-Z]+-\d+)/);
|
|
37
|
+
if (!jiraTicketMatch) {
|
|
38
|
+
throw new Error('No Jira ticket found in the branch name.');
|
|
39
|
+
}
|
|
40
|
+
return jiraTicketMatch[0];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Fetches Jira ticket data from Jira API
|
|
45
|
+
*/
|
|
46
|
+
async function fetchJiraTicketData(jiraTicketId) {
|
|
47
|
+
const jiraBaseUrl = 'https://docupacellc.atlassian.net';
|
|
48
|
+
const jiraEmail = process.env.JIRA_API_EMAIL;
|
|
49
|
+
const jiraApiKey = process.env.JIRA_API_KEY;
|
|
50
|
+
const jiraFullApiKey = `${jiraEmail}:${jiraApiKey}`;
|
|
51
|
+
const jiraUrl = `${jiraBaseUrl}/rest/api/2/issue/${jiraTicketId}`;
|
|
52
|
+
|
|
53
|
+
const jiraResponse = await fetch(jiraUrl, {
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: `Basic ${Buffer.from(jiraFullApiKey).toString('base64')}`,
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!jiraResponse.ok) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Failed to fetch Jira ticket. Status: ${jiraResponse.status}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return await jiraResponse.json();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generates release notes using OpenAI
|
|
71
|
+
*/
|
|
72
|
+
async function generateReleaseNotes(jiraTicketData) {
|
|
73
|
+
const openaiClient = new OpenAI({
|
|
74
|
+
apiKey: process.env['OPENAI_API_KEY'],
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const ChangeRequestSchema = z.object({
|
|
78
|
+
change_type: z.enum(['patch', 'minor']),
|
|
79
|
+
summary: z.string(),
|
|
80
|
+
jira_ticket_id: z.string(),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const openAiResponse = await openaiClient.chat.completions.create({
|
|
84
|
+
model: 'gpt-4o-mini',
|
|
85
|
+
messages: [
|
|
86
|
+
{
|
|
87
|
+
role: 'system',
|
|
88
|
+
content: `Process JSON from a Jira ticket to generate simple maximum 2 sentence release note for the ticket and determine the change type for bug it should be a patch for new feature it should be a minor change`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
role: 'user',
|
|
92
|
+
content: JSON.stringify(jiraTicketData),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
response_format: zodResponseFormat(ChangeRequestSchema, 'changeset_schema'),
|
|
96
|
+
temperature: 0,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const assistantResponse = openAiResponse.choices[0].message.content;
|
|
100
|
+
return JSON.parse(assistantResponse);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Writes the changeset file
|
|
105
|
+
*/
|
|
106
|
+
async function writeChangesetFile(
|
|
107
|
+
componentName,
|
|
108
|
+
jiraTicketId,
|
|
109
|
+
changeType,
|
|
110
|
+
summary,
|
|
111
|
+
) {
|
|
112
|
+
const changesetContent = `---
|
|
113
|
+
"${componentName}": ${changeType}
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
${summary} [${jiraTicketId}]
|
|
117
|
+
`;
|
|
118
|
+
const changesetFile = `./.changeset/${jiraTicketId}.md`;
|
|
119
|
+
|
|
120
|
+
await createChangesetDirectory();
|
|
121
|
+
await fs.promises.writeFile(changesetFile, changesetContent);
|
|
122
|
+
console.log(`Created changeset file at ${changesetFile}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Main function to create a changeset
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
export async function createChangeset() {
|
|
130
|
+
if (isMonoRepo()) {
|
|
131
|
+
const appPackages = await getAllAppPackages();
|
|
132
|
+
const response = await inquirer.prompt([
|
|
133
|
+
{
|
|
134
|
+
type: 'list',
|
|
135
|
+
name: 'package',
|
|
136
|
+
message: 'Which package do you want to generate changesetfor?',
|
|
137
|
+
choices: appPackages.map((appPackage) => appPackage.name),
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const selectedPackage = appPackages.find(
|
|
142
|
+
(appPackage) => appPackage.name == response.package,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const selectedPackageContent = await getPackage(selectedPackage.path);
|
|
146
|
+
await createChangesetForPackage(selectedPackageContent);
|
|
147
|
+
} else {
|
|
148
|
+
const mainPackage = await getMainPackage();
|
|
149
|
+
await createChangesetForPackage(mainPackage);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function createChangesetForPackage(mainPackage) {
|
|
154
|
+
const componentName = mainPackage.name;
|
|
155
|
+
try {
|
|
156
|
+
// Step 1: Get the current branch name
|
|
157
|
+
const branchName = await getCurrentBranch();
|
|
158
|
+
|
|
159
|
+
// Step 2: Extract Jira ticket number
|
|
160
|
+
const jiraTicketId = extractJiraTicketId(branchName);
|
|
161
|
+
|
|
162
|
+
// Step 3: Fetch Jira ticket data
|
|
163
|
+
const jiraTicketData = await fetchJiraTicketData(jiraTicketId);
|
|
164
|
+
const jiraSummary = jiraTicketData.fields.summary;
|
|
165
|
+
console.log(
|
|
166
|
+
`Generating release notes for ${jiraTicketId} - ${jiraSummary}`,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Step 4: Generate release notes using OpenAI
|
|
170
|
+
const { change_type, summary } = await generateReleaseNotes(jiraTicketData);
|
|
171
|
+
console.log(`Change type: ${change_type}`);
|
|
172
|
+
console.log(`Release note: ${summary}`);
|
|
173
|
+
|
|
174
|
+
// Step 5: Create a changeset .md file
|
|
175
|
+
await writeChangesetFile(componentName, jiraTicketId, change_type, summary);
|
|
176
|
+
|
|
177
|
+
console.log('Done!');
|
|
178
|
+
} catch (err) {
|
|
179
|
+
console.error(`Error: ${err.message}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
}
|
|
@@ -2,39 +2,25 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { $ } from 'zx';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { isMonoRepo, setGitInfo } from '../next/util.js';
|
|
6
|
+
import { versionStatus } from './version.js';
|
|
7
|
+
import { getAppReleases } from './cli.js';
|
|
6
8
|
|
|
7
9
|
const CHANGESET_DIR = '.changeset';
|
|
8
10
|
|
|
9
11
|
export async function validateChangesets() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
if (isMonoRepo()) {
|
|
13
|
+
await setGitInfo();
|
|
12
14
|
const destBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
let appPaths;
|
|
21
|
-
if (appPackages.length > 0) {
|
|
22
|
-
appPaths = appPackages.map((appPackage) => appPackage.path);
|
|
23
|
-
for (const appPath of appPaths) {
|
|
24
|
-
const changesetPath = path.resolve(appPath, CHANGESET_DIR);
|
|
25
|
-
await validateChangesetPath(changesetPath);
|
|
26
|
-
}
|
|
27
|
-
} else {
|
|
28
|
-
throw new Error('No changeset for at least one app');
|
|
15
|
+
await $`git fetch origin ${destBranch}:${destBranch}`;
|
|
16
|
+
const releases = await versionStatus(destBranch);
|
|
17
|
+
const appReleases = getAppReleases(releases);
|
|
18
|
+
if (appReleases.length == 0) {
|
|
19
|
+
throw new Error('Must have a changeset for at lease one app');
|
|
29
20
|
}
|
|
30
21
|
} else {
|
|
31
22
|
await validateChangesetPath(path.resolve(process.cwd(), CHANGESET_DIR));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
await setGitInfo();
|
|
35
|
-
const destBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
|
|
36
|
-
await $`git fetch origin ${destBranch}:${destBranch}`;
|
|
37
|
-
await $`changeset status --since=${destBranch}`;
|
|
23
|
+
}
|
|
38
24
|
}
|
|
39
25
|
|
|
40
26
|
export async function validateChangesetPath(directoryPath) {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { setGitInfo } from '../next/util.js';
|
|
2
|
+
import { $ } from 'zx';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { updateComponentVersion } from '../next/jira.js';
|
|
5
|
+
|
|
6
|
+
const changeSetPackage = `{
|
|
7
|
+
"name": "changeset-package",
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"description": "",
|
|
10
|
+
"scripts": {
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"dependencies": {},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@changesets/cli": "^2.27.10"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export async function versionStatus(sinceBranch) {
|
|
24
|
+
const outputPath = './changeset-output.json';
|
|
25
|
+
|
|
26
|
+
if (sinceBranch) {
|
|
27
|
+
await $`pnpm changeset status --output=${outputPath} --since=${sinceBranch}`;
|
|
28
|
+
} else {
|
|
29
|
+
await $`pnpm changeset status --output=${outputPath}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Read the file and parse JSON
|
|
33
|
+
const jsonOutput = JSON.parse(await fs.promises.readFile(outputPath, 'utf8'));
|
|
34
|
+
|
|
35
|
+
await fs.promises.rm(outputPath);
|
|
36
|
+
return jsonOutput.releases;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function versionCommand(closeVersion = false) {
|
|
40
|
+
console.log('📦 Running Changeset versioning...');
|
|
41
|
+
await setGitInfo();
|
|
42
|
+
await $`mv package.json package.json.bak`;
|
|
43
|
+
await $`mv pnpm-lock.yaml pnpm-lock.yaml.bak`;
|
|
44
|
+
await fs.promises.writeFile('package.json', changeSetPackage);
|
|
45
|
+
await $`pnpm install`;
|
|
46
|
+
await $`rm package.json pnpm-lock.yaml`;
|
|
47
|
+
await $`mv package.json.bak package.json`;
|
|
48
|
+
await $`mv pnpm-lock.yaml.bak pnpm-lock.yaml`;
|
|
49
|
+
if (closeVersion) {
|
|
50
|
+
await $`pnpm changeset pre exit`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const releases = await versionStatus();
|
|
55
|
+
|
|
56
|
+
await $`pnpm changeset version`;
|
|
57
|
+
|
|
58
|
+
return releases;
|
|
59
|
+
} catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function updateJiraWithVersion(releases) {
|
|
65
|
+
const lastCommit = (await $`git log -1 --pretty=%B`).stdout.trim();
|
|
66
|
+
console.log(`Last commit '${lastCommit}' being processed to get Jira ticket`);
|
|
67
|
+
|
|
68
|
+
const jiraTicketMatch = lastCommit.match(/([A-Z]+-\d+)/);
|
|
69
|
+
const jiraTicketId = jiraTicketMatch ? jiraTicketMatch[0] : null;
|
|
70
|
+
|
|
71
|
+
// If we have all the necessary data, update the component version in Jira
|
|
72
|
+
if (jiraTicketId) {
|
|
73
|
+
for (const release of releases) {
|
|
74
|
+
try {
|
|
75
|
+
await updateComponentVersion(
|
|
76
|
+
jiraTicketId,
|
|
77
|
+
release.name,
|
|
78
|
+
release.newVersion,
|
|
79
|
+
);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(
|
|
82
|
+
`Failed to update Jira component version: ${error.message}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log('✅ Changeset versioning complete.');
|
|
89
|
+
}
|
package/src/next/jira.js
CHANGED
|
@@ -12,11 +12,6 @@ const jiraClient = axios.create({
|
|
|
12
12
|
},
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
const packageComponents = {
|
|
16
|
-
'docupace-ui': 'next-ui',
|
|
17
|
-
'docupace-graphql-server': 'graphql-server',
|
|
18
|
-
'@docupace/template': 'template',
|
|
19
|
-
};
|
|
20
15
|
/**
|
|
21
16
|
* Updates the component version custom field in Jira
|
|
22
17
|
* @param {string} issueKey - The Jira issue key
|
|
@@ -24,7 +19,7 @@ const packageComponents = {
|
|
|
24
19
|
* @param {string} version - The version to set
|
|
25
20
|
*/
|
|
26
21
|
export async function updateComponentVersion(issueKey, packageName, version) {
|
|
27
|
-
const component =
|
|
22
|
+
const component = packageName;
|
|
28
23
|
console.log(
|
|
29
24
|
`Updating ${issueKey} for package ${packageName} translated to component ${component} with version ${version}`,
|
|
30
25
|
);
|
package/src/next/publish.js
CHANGED
|
@@ -56,12 +56,13 @@ export async function publish(gitSha) {
|
|
|
56
56
|
await $`docker tag ${IMAGE_NAME} ${JF_REGISTRY_FULL_URL}/${BUILD_IMAGE_NAME}`;
|
|
57
57
|
await $`docker push ${JF_REGISTRY_FULL_URL}/${BUILD_IMAGE_NAME}`;
|
|
58
58
|
|
|
59
|
-
const
|
|
59
|
+
const gitTags = (await $`git tag --points-at HEAD`).stdout
|
|
60
60
|
.trim()
|
|
61
61
|
.split('\n')
|
|
62
62
|
.filter(Boolean);
|
|
63
|
-
for (const
|
|
64
|
-
const
|
|
63
|
+
for (const gitTag of gitTags) {
|
|
64
|
+
const version = gitTag.includes('@') ? 'v' + gitTag.split('@')[1] : gitTag;
|
|
65
|
+
const tagName = `${IMAGE_PREFIX}:${version}`;
|
|
65
66
|
await $`docker tag ${IMAGE_NAME} ${JF_REGISTRY_FULL_URL}/${tagName}`;
|
|
66
67
|
await $`docker push ${JF_REGISTRY_FULL_URL}/${tagName}`;
|
|
67
68
|
}
|
package/src/next/util.js
CHANGED
|
@@ -3,8 +3,6 @@ import { join } from 'path';
|
|
|
3
3
|
import { $, nothrow } from 'zx';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
|
|
6
|
-
const GIT_SHA = (await $`git log --pretty=format:'%h' -n 1`).stdout.trim();
|
|
7
|
-
|
|
8
6
|
export function isMonoRepo() {
|
|
9
7
|
// Check for the presence of turbo.json which indicates a Turborepo monorepo
|
|
10
8
|
return existsSync(join(process.cwd(), 'turbo.json'));
|
|
@@ -41,6 +39,33 @@ export async function getChangedPackagesInternal(fromRef, toRef) {
|
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
|
|
42
|
+
export async function getAllPackages() {
|
|
43
|
+
try {
|
|
44
|
+
const turboDryOutput = await $`turbo run build --dry=json`;
|
|
45
|
+
const dryRunJson = turboDryOutput.stdout.trim();
|
|
46
|
+
const dryRun = JSON.parse(dryRunJson);
|
|
47
|
+
|
|
48
|
+
const packages = dryRun.tasks.map((task) => ({
|
|
49
|
+
name: task.package,
|
|
50
|
+
directory: task.directory,
|
|
51
|
+
path: path.resolve(process.cwd(), task.directory),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
return packages;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('Error getting changed packages:', error);
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function getAllAppPackages() {
|
|
62
|
+
const changedPackages = await getAllPackages();
|
|
63
|
+
const appPackages = changedPackages.filter((appPackage) =>
|
|
64
|
+
appPackage.directory.startsWith('apps'),
|
|
65
|
+
);
|
|
66
|
+
return appPackages;
|
|
67
|
+
}
|
|
68
|
+
|
|
44
69
|
export async function getChangedPackages() {
|
|
45
70
|
const branch = process.env.BITBUCKET_BRANCH;
|
|
46
71
|
const destBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
|
|
@@ -60,24 +85,6 @@ export async function getChangedAppPackages() {
|
|
|
60
85
|
return appPackages;
|
|
61
86
|
}
|
|
62
87
|
|
|
63
|
-
export async function buildChangePackages() {
|
|
64
|
-
const appPackages = await getChangedAppPackages();
|
|
65
|
-
if (appPackages.length == 0) {
|
|
66
|
-
throw new Error('Nothing to build');
|
|
67
|
-
}
|
|
68
|
-
for (const appPackage of appPackages) {
|
|
69
|
-
await dockerBuild(appPackage);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function dockerBuild(appPackage) {
|
|
74
|
-
process.env.DOCKER_HOST = '';
|
|
75
|
-
const shortName = appPackage.name.replace('@docupace/', '');
|
|
76
|
-
const imageName = `${process.env.BITBUCKET_REPO_SLUG}-${shortName}:sha-${GIT_SHA}-withMaps`;
|
|
77
|
-
console.log(`Building Docker image ${imageName}...`);
|
|
78
|
-
await $`docker build --secret id=npm_token,env=DOCUPACE_NPM_TOKEN . --file ${path.resolve(appPackage.path, 'Dockerfile')} --target builder --tag ${imageName}`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
88
|
export async function setGitInfo() {
|
|
82
89
|
await $`git config --global user.email "gitops@docupace.com"`;
|
|
83
90
|
await $`git config --global user.name "Gitops"`;
|