Skip to content

Commit 7e20bec

Browse files
fix(release): update-publishing-logic (#320)
Co-authored-by: Shreyas Sharma <shreysh2@cisco.com>
1 parent 445e8a7 commit 7e20bec

File tree

6 files changed

+237
-60
lines changed

6 files changed

+237
-60
lines changed

.releaserc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
"@semantic-release/commit-analyzer",
1111
"@semantic-release/release-notes-generator",
1212
"@semantic-release/changelog",
13+
[
14+
"@semantic-release/exec",
15+
{
16+
"prepareCmd": "node ./tooling/src/publish.js ${nextRelease.channel} ${nextRelease.version} "
17+
}
18+
],
1319
[
1420
"@semantic-release/git",
1521
{

package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
],
1212
"packageManager": "yarn@4.5.1",
1313
"devDependencies": {
14+
"@babel/preset-typescript": "7.25.9",
1415
"@semantic-release/changelog": "^6.0.3",
1516
"@semantic-release/exec": "^6.0.3",
1617
"@semantic-release/git": "^10.0.1",
1718
"@semantic-release/github": "^11.0.1",
1819
"html-webpack-plugin": "^5.6.3",
20+
"jest": "29.7.0",
1921
"node-gyp": "^10.2.0",
2022
"semantic-release": "^24.2.0",
2123
"typescript": "^5.6.3",
@@ -24,6 +26,7 @@
2426
"webpack-dev-server": "^5.1.0"
2527
},
2628
"scripts": {
29+
"test:unit": "jest --coverage",
2730
"build": "yarn run build:store && yarn run build:widgets",
2831
"build:store": "yarn workspace @webex/cc-store build:src",
2932
"build:widgets": "yarn workspace @webex/cc-station-login build:src && yarn workspace @webex/cc-user-state build:src",
@@ -32,5 +35,10 @@
3235
"samples:serve-react": "yarn workspace @webex/react-samples-app serve",
3336
"samples:serve-wc": "yarn workspace @webex/web-component-samples-app serve",
3437
"release:widgets": "semantic-release"
38+
},
39+
"jest": {
40+
"testMatch": [
41+
"**/tooling/tests/*.js"
42+
]
3543
}
3644
}

publish.js

Lines changed: 0 additions & 60 deletions
This file was deleted.

tooling/src/publish.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const {execSync} = require('child_process');
2+
const fs = require('fs');
3+
const path = require('path');
4+
5+
// Function to remove the 'stableVersion' key
6+
function removeStableVersion(packageJsonPath) {
7+
try {
8+
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');
9+
const packageData = JSON.parse(packageJsonContent);
10+
11+
if (packageData.hasOwnProperty('stableVersion')) {
12+
delete packageData.stableVersion;
13+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageData, null, 2), 'utf-8');
14+
console.log("'stableVersion' key removed successfully.");
15+
} else {
16+
console.log("'stableVersion' key does not exist in package.json.");
17+
}
18+
} catch (error) {
19+
console.error("An error occurred while removing 'stableVersion':", error.message);
20+
}
21+
}
22+
23+
function versionAndPublish() {
24+
const branchName = process.argv[2];
25+
const newVersion = process.argv[3];
26+
27+
if (!branchName || !newVersion) {
28+
console.error(
29+
'Error: Not enough positional arguments provided! node <relative_path_to_publish> <branchName> <nextVersion>'
30+
);
31+
process.exit(1);
32+
}
33+
const contactCenterPath = './packages/contact-center';
34+
const dependencies = ['@webex/cc-store'];
35+
36+
try {
37+
const ccFolder = fs
38+
.readdirSync(contactCenterPath, {withFileTypes: true})
39+
.filter((dirent) => dirent.isDirectory())
40+
.map((dirent) => dirent.name);
41+
// Separate dependency workspaces and other workspaces
42+
const dependencyWorkspaces = ccFolder.filter((fileName) => dependencies.includes(`@webex/cc-${fileName}`));
43+
44+
const otherWorkspaces = ccFolder.filter((fileName) => !dependencies.includes(`@webex/cc-${fileName}`));
45+
46+
const publishWorkspace = (fileName) => {
47+
const workspaceName = `@webex/cc-${fileName}`;
48+
console.log(`Running publish script for ${workspaceName}: ${newVersion}`);
49+
50+
console.log(`Removing stable version from package.json for ${workspaceName}`);
51+
const packageJsonPath = path.join(contactCenterPath, fileName, 'package.json');
52+
removeStableVersion(packageJsonPath);
53+
54+
console.log(`Publishing new version for ${workspaceName}: ${newVersion}`);
55+
56+
// Update version in the workspace
57+
execSync(`yarn workspace ${workspaceName} version ${newVersion}`, {stdio: 'inherit'});
58+
59+
// Publish the package
60+
execSync(`yarn workspace ${workspaceName} npm publish --tag ${branchName}`, {stdio: 'inherit'});
61+
};
62+
63+
// Publish dependencies first
64+
dependencyWorkspaces.forEach(publishWorkspace);
65+
66+
// Publish other packages
67+
otherWorkspaces.forEach(publishWorkspace);
68+
} catch (error) {
69+
console.error(`Failed to process workspaces:`, error.message);
70+
process.exit(1);
71+
}
72+
}
73+
74+
// Only execute when called through a module/script
75+
if (require.main !== module) {
76+
// Export the function for testing
77+
module.exports = {versionAndPublish};
78+
} else {
79+
versionAndPublish();
80+
}

tooling/tests/publish.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
jest.mock('fs');
5+
jest.mock('child_process');
6+
const {versionAndPublish} = require('../src/publish');
7+
8+
const mockFs = require('fs');
9+
10+
jest.spyOn(console, 'log').mockImplementation(() => {});
11+
jest.spyOn(console, 'error').mockImplementation(() => {});
12+
jest.spyOn(process, 'exit').mockImplementation(() => {});
13+
14+
describe('versionAndPublish', () => {
15+
const packageJsonPath = 'packages/contact-center/test-workspace/package.json';
16+
beforeEach(() => {
17+
jest.resetAllMocks();
18+
});
19+
test('Exits if we dont have enough arguments', () => {
20+
jest.spyOn(process, 'exit').mockImplementation(() => {});
21+
process.argv = ['node', 'script.js', 'main'];
22+
versionAndPublish();
23+
24+
expect(console.error).toHaveBeenCalledWith(
25+
'Error: Not enough positional arguments provided! node <relative_path_to_publish> <branchName> <nextVersion>'
26+
);
27+
expect(process.exit).toHaveBeenCalledWith(1);
28+
29+
process.argv = ['node', 'script.js', '1.2.3-test.12'];
30+
versionAndPublish();
31+
32+
expect(console.error).toHaveBeenCalledWith(
33+
'Error: Not enough positional arguments provided! node <relative_path_to_publish> <branchName> <nextVersion>'
34+
);
35+
expect(process.exit).toHaveBeenCalledWith(1);
36+
});
37+
38+
test('removes "stableVersion" key from package.json when it exists', () => {
39+
const packageJsonContent = JSON.stringify({
40+
name: 'test-package',
41+
version: '1.0.0',
42+
stableVersion: '1.0.0',
43+
});
44+
jest.spyOn(fs, 'readdirSync').mockReturnValue([{name: 'test-workspace', isDirectory: () => true}]);
45+
jest.spyOn(fs.Dirent.prototype, 'isDirectory').mockReturnValue(true);
46+
mockFs.readFileSync.mockReturnValue(packageJsonContent);
47+
mockFs.writeFileSync.mockImplementation(() => {});
48+
49+
process.argv = ['node', 'script.js', 'main', '1.3.3-test.1'];
50+
versionAndPublish();
51+
52+
const expectedPackageJsonContent = JSON.stringify({name: 'test-package', version: '1.0.0'}, null, 2);
53+
54+
expect(mockFs.writeFileSync).toHaveBeenCalledWith(packageJsonPath, expectedPackageJsonContent, 'utf-8');
55+
expect(console.log).toHaveBeenCalledWith("'stableVersion' key removed successfully.");
56+
});
57+
58+
test('error occured while writting "stableVersion" key to', () => {
59+
const packageJsonContent = JSON.stringify({
60+
name: 'test-package',
61+
version: '1.0.0',
62+
stableVersion: '1.0.0',
63+
});
64+
jest.spyOn(fs, 'readdirSync').mockReturnValue([{name: 'test-workspace', isDirectory: () => true}]);
65+
jest.spyOn(fs.Dirent.prototype, 'isDirectory').mockReturnValue(true);
66+
mockFs.writeFileSync.mockImplementation(() => {
67+
throw new Error('Error while writing to file');
68+
});
69+
70+
process.argv = ['node', 'script.js', 'main', '1.3.3-test.1'];
71+
versionAndPublish();
72+
73+
expect(console.error).toHaveBeenCalledWith(
74+
"An error occurred while removing 'stableVersion':",
75+
'"undefined" is not valid JSON'
76+
);
77+
});
78+
79+
test('skips removing "stableVersion" if it does not exist', () => {
80+
const packageJsonContent = JSON.stringify({
81+
name: 'test-package',
82+
version: '1.0.0',
83+
});
84+
jest.spyOn(fs, 'readdirSync').mockReturnValue([{name: 'test-workspace', isDirectory: () => true}]);
85+
jest.spyOn(fs.Dirent.prototype, 'isDirectory').mockReturnValue(true);
86+
mockFs.readFileSync.mockReturnValue(packageJsonContent);
87+
mockFs.writeFileSync.mockImplementation(() => {});
88+
89+
process.argv = ['node', 'script.js', 'main', '1.3.3-test.1'];
90+
versionAndPublish();
91+
92+
expect(mockFs.writeFileSync).not.toHaveBeenCalled();
93+
expect(console.log).toHaveBeenCalledWith("'stableVersion' key does not exist in package.json.");
94+
});
95+
96+
test('publishes dependencies first and other workspaces afterward', () => {
97+
const contactCenterPath = './packages/contact-center';
98+
const dependencies = ['@webex/cc-store'];
99+
100+
mockFs.readdirSync.mockReturnValue([
101+
{name: 'store', isDirectory: () => true},
102+
{name: 'station-login', isDirectory: () => true},
103+
]);
104+
105+
mockFs.readFileSync.mockReturnValue('{}');
106+
mockFs.writeFileSync.mockImplementation(() => {});
107+
108+
const mockExecSync = require('child_process').execSync;
109+
mockExecSync.mockImplementation(() => {});
110+
111+
const processArgvMock = ['node', 'script.js', 'main', '1.0.1'];
112+
process.argv = processArgvMock;
113+
114+
versionAndPublish();
115+
116+
// Check the dependency workspace was published first
117+
expect(mockExecSync).toHaveBeenNthCalledWith(1, 'yarn workspace @webex/cc-store version 1.0.1', {stdio: 'inherit'});
118+
119+
expect(mockExecSync).toHaveBeenNthCalledWith(2, 'yarn workspace @webex/cc-store npm publish --tag main', {
120+
stdio: 'inherit',
121+
});
122+
123+
// Check the non-dependency workspace was published afterward
124+
expect(mockExecSync).toHaveBeenNthCalledWith(3, 'yarn workspace @webex/cc-station-login version 1.0.1', {
125+
stdio: 'inherit',
126+
});
127+
128+
expect(mockExecSync).toHaveBeenNthCalledWith(4, 'yarn workspace @webex/cc-station-login npm publish --tag main', {
129+
stdio: 'inherit',
130+
});
131+
});
132+
133+
it('should export versionAndPublish when required as a module', () => {
134+
jest.resetModules();
135+
// Simulate require.main !== module (require the module as a normal import)
136+
const script = require('../src/publish');
137+
138+
// Check if versionAndPublish function is exported
139+
expect(script).toHaveProperty('versionAndPublish');
140+
});
141+
});

yarn.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24277,11 +24277,13 @@ __metadata:
2427724277
version: 0.0.0-use.local
2427824278
resolution: "webex-widgets@workspace:."
2427924279
dependencies:
24280+
"@babel/preset-typescript": "npm:7.25.9"
2428024281
"@semantic-release/changelog": "npm:^6.0.3"
2428124282
"@semantic-release/exec": "npm:^6.0.3"
2428224283
"@semantic-release/git": "npm:^10.0.1"
2428324284
"@semantic-release/github": "npm:^11.0.1"
2428424285
html-webpack-plugin: "npm:^5.6.3"
24286+
jest: "npm:29.7.0"
2428524287
node-gyp: "npm:^10.2.0"
2428624288
semantic-release: "npm:^24.2.0"
2428724289
typescript: "npm:^5.6.3"

0 commit comments

Comments
 (0)