@hexonet/semantic-release-whmcs 5.1.2 → 5.1.4
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/HISTORY.md +14 -0
- package/README.md +1 -0
- package/index.js +86 -5
- package/lib/delete-marketplace-version.js +3 -3
- package/lib/publish.js +29 -11
- package/lib/puppet-utils.js +1 -1
- package/lib/puppet.js +9 -9
- package/lib/resolve-config.js +14 -11
- package/lib/set-compatible-versions.js +21 -11
- package/package.json +3 -3
package/HISTORY.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [5.1.4](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/compare/v5.1.3...v5.1.4) (2025-09-05)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* **deps:** bump puppeteer from 24.18.0 to 24.19.0 ([c6557bf](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/commit/c6557bfd3fa0d146ea6d1f1427e57db0e5ffd7cd))
|
7
|
+
|
8
|
+
## [5.1.3](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/compare/v5.1.2...v5.1.3) (2025-09-04)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* **core:** implement timeout handling for publish operations and improve error handling in compatibility updates ([5fc8b34](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/commit/5fc8b345a8412e0ec0bfa92922fe2939af84224a))
|
14
|
+
|
1
15
|
## [5.1.2](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/compare/v5.1.1...v5.1.2) (2025-09-01)
|
2
16
|
|
3
17
|
|
package/README.md
CHANGED
@@ -76,6 +76,7 @@ That said, before you can use this module for publishing new product/module vers
|
|
76
76
|
| `PUPPETEER_HEADLESS` | **Optional.** Toggle headless mode on/off. by default true. Values: 1,0. |
|
77
77
|
| `GH_TOKEN` | **Optional.** GitHub API authentication token to use for syncing versions. |
|
78
78
|
| `GH_REPO` | **Optional.** GitHub repository name (format: organization/repository) to use for syncing versions. |
|
79
|
+
| `useCookieExtension` | **Optional.** Use cookies extension when puppeteer is running to avoid cookie banner disruptions. |
|
79
80
|
|
80
81
|
### Options
|
81
82
|
|
package/index.js
CHANGED
@@ -6,6 +6,7 @@ import githubReleases from "./lib/get-github-releases.js";
|
|
6
6
|
import marketplaceVersions from "./lib/scrape-marketplace-versions.js";
|
7
7
|
|
8
8
|
let verified;
|
9
|
+
let whmcsPublishResult = null;
|
9
10
|
|
10
11
|
/**
|
11
12
|
* Called by semantic-release during the verify step
|
@@ -26,9 +27,30 @@ async function verifyConditions(pluginConfig, context) {
|
|
26
27
|
*/
|
27
28
|
async function publish(pluginConfig, context) {
|
28
29
|
await verifyConditions(pluginConfig, context);
|
29
|
-
|
30
|
+
|
31
|
+
const result = await publishWHMCS(pluginConfig, context);
|
32
|
+
|
33
|
+
// Store the result globally for success/fail hooks
|
34
|
+
whmcsPublishResult = {
|
35
|
+
success: !!result,
|
36
|
+
version: context.nextRelease?.version,
|
37
|
+
result: result,
|
38
|
+
};
|
39
|
+
|
40
|
+
if (result) {
|
41
|
+
console.log(`✅ WHMCS: Published version ${context.nextRelease?.version}`);
|
42
|
+
} else {
|
43
|
+
console.log(`❌ WHMCS: Failed to publish version ${context.nextRelease?.version}`);
|
44
|
+
}
|
45
|
+
|
46
|
+
return result;
|
30
47
|
}
|
31
48
|
|
49
|
+
/**
|
50
|
+
* Sync versions from GitHub releases to WHMCS marketplace
|
51
|
+
* @param {*} pluginConfig The semantic-release plugin config
|
52
|
+
* @param {*} context The context provided by semantic-release
|
53
|
+
*/
|
32
54
|
async function syncVersions(pluginConfig, context) {
|
33
55
|
await verifyConditions(pluginConfig, context);
|
34
56
|
const releases = await githubReleases(pluginConfig, context);
|
@@ -43,29 +65,88 @@ async function syncVersions(pluginConfig, context) {
|
|
43
65
|
releaseDate: release.published_at,
|
44
66
|
};
|
45
67
|
console.log(`Adding missing version ${context.nextRelease.version}`);
|
46
|
-
await publish(pluginConfig, context);
|
68
|
+
const result = await publish(pluginConfig, context);
|
69
|
+
console.log(`Published version ${context.nextRelease.version}:`, result);
|
47
70
|
}
|
48
71
|
}
|
49
72
|
}
|
73
|
+
return undefined;
|
50
74
|
}
|
51
75
|
|
76
|
+
/**
|
77
|
+
* Delete a version from WHMCS marketplace
|
78
|
+
* @param {*} pluginConfig The semantic-release plugin config
|
79
|
+
* @param {*} context The context provided by semantic-release
|
80
|
+
*/
|
52
81
|
async function delVersion(pluginConfig, context) {
|
53
82
|
await verifyConditions(pluginConfig, context);
|
54
|
-
await deleteVersion(pluginConfig, context);
|
83
|
+
const result = await deleteVersion(pluginConfig, context);
|
84
|
+
return result;
|
55
85
|
}
|
56
86
|
|
87
|
+
/**
|
88
|
+
* Update compatibility versions for a product
|
89
|
+
* @param {*} pluginConfig The semantic-release plugin config
|
90
|
+
* @param {*} context The context provided by semantic-release
|
91
|
+
*/
|
57
92
|
async function updateCompatibility(pluginConfig, context) {
|
58
93
|
await verifyConditions(pluginConfig, context);
|
59
|
-
await setCompatibleVersions(pluginConfig, context);
|
94
|
+
const result = await setCompatibleVersions(pluginConfig, context);
|
95
|
+
return result;
|
60
96
|
}
|
61
97
|
|
98
|
+
// /**
|
99
|
+
// * Called by semantic-release when a release succeeds
|
100
|
+
// * @param {*} pluginConfig The semantic-release plugin config
|
101
|
+
// * @param {*} context The context provided by semantic-release
|
102
|
+
// */
|
103
|
+
// async function success(pluginConfig, context) {
|
104
|
+
// if (whmcsPublishResult) {
|
105
|
+
// if (whmcsPublishResult.success) {
|
106
|
+
// console.log(`🎉 WHMCS: Successfully published version ${whmcsPublishResult.version} to marketplace`);
|
107
|
+
// if (whmcsPublishResult.result?.url) {
|
108
|
+
// console.log(`🔗 WHMCS: ${whmcsPublishResult.result.url}`);
|
109
|
+
// }
|
110
|
+
// } else {
|
111
|
+
// console.log(`⚠️ WHMCS: Version ${whmcsPublishResult.version} was NOT published to marketplace (WHMCS publish failed)`);
|
112
|
+
// }
|
113
|
+
// } else {
|
114
|
+
// console.log(`⚠️ WHMCS: No WHMCS publish attempted during this release`);
|
115
|
+
// }
|
116
|
+
// }
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Called by semantic-release when a release fails
|
120
|
+
* @param {*} pluginConfig The semantic-release plugin config
|
121
|
+
* @param {*} context The context provided by semantic-release
|
122
|
+
*/
|
62
123
|
async function fail(pluginConfig, context) {
|
63
|
-
|
124
|
+
if (whmcsPublishResult) {
|
125
|
+
if (whmcsPublishResult.success) {
|
126
|
+
console.log(`✅ WHMCS: Version ${whmcsPublishResult.version} was successfully published to marketplace`);
|
127
|
+
console.log(`ℹ️ WHMCS: Release failed due to other plugins, not WHMCS`);
|
128
|
+
} else {
|
129
|
+
console.log(`❌ WHMCS: Failed to publish version ${whmcsPublishResult.version} to marketplace`);
|
130
|
+
}
|
131
|
+
} else {
|
132
|
+
console.log(`ℹ️ WHMCS: No WHMCS publish was attempted before failure`);
|
133
|
+
}
|
134
|
+
|
135
|
+
// Also show any WHMCS-specific errors from context
|
136
|
+
const { errors } = context;
|
137
|
+
const whmcsErrors = errors?.filter(
|
138
|
+
(e) => e.message?.toLowerCase().includes("whmcs") || e.message?.toLowerCase().includes("marketplace")
|
139
|
+
);
|
140
|
+
|
141
|
+
if (whmcsErrors?.length > 0) {
|
142
|
+
console.log(`🔍 WHMCS: ${whmcsErrors.map((e) => e.message).join(", ")}`);
|
143
|
+
}
|
64
144
|
}
|
65
145
|
|
66
146
|
export default {
|
67
147
|
verifyConditions,
|
68
148
|
publish,
|
149
|
+
//success,
|
69
150
|
fail,
|
70
151
|
syncVersions,
|
71
152
|
delVersion,
|
@@ -88,8 +88,8 @@ export default async (pluginConfig, context) => {
|
|
88
88
|
}
|
89
89
|
|
90
90
|
// Click the confirmation button and wait for navigation/alert
|
91
|
-
await clickAndWaitForResult(page,
|
92
|
-
await wait(
|
91
|
+
await clickAndWaitForResult(page, "button.btn-styled-red", { navOpts });
|
92
|
+
await wait(300);
|
93
93
|
debug("Checking for alert-success after confirmation...");
|
94
94
|
const result = await waitForSubmitResult(page, { timeout: navOpts.timeout });
|
95
95
|
if (result === "error") {
|
@@ -103,7 +103,7 @@ export default async (pluginConfig, context) => {
|
|
103
103
|
} else {
|
104
104
|
// Fallback: check if the version row is gone
|
105
105
|
debug("No success or error alert appeared after delete. Checking if version row is gone...");
|
106
|
-
await page.waitForSelector("table", { timeout:
|
106
|
+
await page.waitForSelector("table", { timeout: 1000 });
|
107
107
|
const rowsAfter = await page.$$("tr");
|
108
108
|
for (const row of rowsAfter) {
|
109
109
|
const textContent = await row.evaluate((el) => el.textContent);
|
package/lib/publish.js
CHANGED
@@ -23,6 +23,7 @@ export default async (pluginConfig, context) => {
|
|
23
23
|
const cleanedNotes = notes.replace(/\[([^[\]]*)\]\([^()]*\)/gm, "$1");
|
24
24
|
|
25
25
|
let page, püppi, urlbase, productid, gotoOpts, selectorOpts;
|
26
|
+
let success = false;
|
26
27
|
|
27
28
|
debug(`Release Version: ${version}`);
|
28
29
|
debug(`Notes: ${notes}`);
|
@@ -77,25 +78,42 @@ export default async (pluginConfig, context) => {
|
|
77
78
|
const result = await waitForSubmitResult(page, { timeout: selectorOpts.timeout || 15000 });
|
78
79
|
if (result === "error") {
|
79
80
|
debug("Publish failed: error alert shown.");
|
80
|
-
|
81
|
-
return false;
|
81
|
+
success = false;
|
82
82
|
} else if (result === "success") {
|
83
83
|
debug("Publish succeeded.");
|
84
|
+
success = true;
|
84
85
|
} else {
|
85
86
|
debug("No success or error alert appeared after submit.");
|
87
|
+
success = false;
|
86
88
|
}
|
87
89
|
|
88
|
-
|
90
|
+
// Call setCompatibleVersions with proper error handling
|
91
|
+
if (success) {
|
92
|
+
try {
|
93
|
+
await setCompatibleVersions(pluginConfig, context);
|
94
|
+
debug("Compatible versions set successfully.");
|
95
|
+
} catch (compatError) {
|
96
|
+
debug("Setting compatible versions failed:", compatError.message);
|
97
|
+
// Don't fail the main publish, just log the error
|
98
|
+
}
|
99
|
+
}
|
89
100
|
} catch (error) {
|
90
101
|
debug("Publishing new product version failed.", error.message);
|
91
|
-
|
92
|
-
|
102
|
+
success = false;
|
103
|
+
} finally {
|
104
|
+
if (page) {
|
105
|
+
await safeClose(page);
|
106
|
+
}
|
93
107
|
}
|
94
|
-
debug("Publishing new product version succeeded.");
|
95
|
-
await safeClose(page);
|
96
108
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
109
|
+
if (success) {
|
110
|
+
debug("Publishing new product version succeeded.");
|
111
|
+
return {
|
112
|
+
name: "WHMCS Marketplace Product Version",
|
113
|
+
url: `${urlbase}/product/${productid}`,
|
114
|
+
};
|
115
|
+
} else {
|
116
|
+
debug("Publishing new product version failed.");
|
117
|
+
return false;
|
118
|
+
}
|
101
119
|
};
|
package/lib/puppet-utils.js
CHANGED
@@ -54,7 +54,7 @@ export async function clickAndWaitForResult(page, selector, { navOpts, resultTim
|
|
54
54
|
let navigated = false;
|
55
55
|
try {
|
56
56
|
await Promise.all([
|
57
|
-
page.waitForNavigation({ waitUntil: "
|
57
|
+
page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: navOpts?.timeout || 10000 }),
|
58
58
|
btn.evaluate((el) => el.click()),
|
59
59
|
]);
|
60
60
|
navigated = true;
|
package/lib/puppet.js
CHANGED
@@ -3,7 +3,7 @@ import resolveConfig from "./resolve-config.js";
|
|
3
3
|
import debugConfig from "debug";
|
4
4
|
import path from "path";
|
5
5
|
import { fileURLToPath } from "url";
|
6
|
-
import { waitForNavigationOrSelector, robustType, wait } from "./puppet-utils.js";
|
6
|
+
import { waitForNavigationOrSelector, robustType, wait, safeClose } from "./puppet-utils.js";
|
7
7
|
const debug = debugConfig("semantic-release:whmcs");
|
8
8
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
@@ -16,14 +16,14 @@ export default async (context) => {
|
|
16
16
|
// logger: logger,
|
17
17
|
gotoOpts: {
|
18
18
|
waitUntil: ["load", "domcontentloaded"],
|
19
|
-
timeout:
|
19
|
+
timeout: 3 * 1000, // 3 seconds
|
20
20
|
},
|
21
21
|
navOpts: {
|
22
22
|
waitUntil: ["networkidle0"],
|
23
|
-
timeout:
|
23
|
+
timeout: 6 * 1000, // 6 seconds
|
24
24
|
},
|
25
25
|
selectorOpts: {
|
26
|
-
timeout:
|
26
|
+
timeout: 6 * 1000, // 6 seconds
|
27
27
|
},
|
28
28
|
logger: context.logger,
|
29
29
|
};
|
@@ -51,7 +51,7 @@ export default async (context) => {
|
|
51
51
|
}
|
52
52
|
|
53
53
|
const browser = await puppeteer.launch({
|
54
|
-
headless: cfg.headless === "1" ?
|
54
|
+
headless: cfg.headless === "1" ? true : false,
|
55
55
|
defaultViewport: null, // automatically full-sized
|
56
56
|
args: launchArgs,
|
57
57
|
});
|
@@ -92,7 +92,7 @@ export default async (context) => {
|
|
92
92
|
const { redirected, selectorFound, url } = await waitForNavigationOrSelector(page, {
|
93
93
|
urlPart: "/account",
|
94
94
|
selector: ".account-navbar",
|
95
|
-
timeout:
|
95
|
+
timeout: 15000,
|
96
96
|
});
|
97
97
|
if (!redirected && !selectorFound) {
|
98
98
|
// Check for alert-danger (login error) on the same page
|
@@ -103,7 +103,7 @@ export default async (context) => {
|
|
103
103
|
debug("Login failed: no navigation, no selector, and no error alert after submit");
|
104
104
|
}
|
105
105
|
// await page.screenshot({ path: `login-failed-no-redirect-or-selector.png` });
|
106
|
-
await page
|
106
|
+
await safeClose(page);
|
107
107
|
return false;
|
108
108
|
}
|
109
109
|
debug("WHMCS Marketplace login succeeded (redirected: %s, selectorFound: %s)", redirected, selectorFound);
|
@@ -114,7 +114,7 @@ export default async (context) => {
|
|
114
114
|
} catch (e) {
|
115
115
|
debug("Screenshot failed", e.message);
|
116
116
|
}
|
117
|
-
await page
|
117
|
+
await safeClose(page);
|
118
118
|
return false;
|
119
119
|
}
|
120
120
|
|
@@ -122,7 +122,7 @@ export default async (context) => {
|
|
122
122
|
let tmp = productid;
|
123
123
|
if (!tmp || !/^[0-9]+$/.test(productid) || !parseInt(productid, 10)) {
|
124
124
|
debug("No or invalid WHMCS Marketplace Product ID provided.");
|
125
|
-
await page
|
125
|
+
await safeClose(page);
|
126
126
|
return false;
|
127
127
|
}
|
128
128
|
tmp = tmp.replace(/(.)/g, "$&\u200E");
|
package/lib/resolve-config.js
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
-
export default (
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
export default (context) => {
|
2
|
+
const env = context?.env || process.env;
|
3
|
+
return {
|
4
|
+
login: env.WHMCSMP_LOGIN || false,
|
5
|
+
password: env.WHMCSMP_PASSWORD || false,
|
6
|
+
productid: env.WHMCSMP_PRODUCTID || false,
|
7
|
+
minversion: env.WHMCSMP_MINVERSION || "7.10",
|
8
|
+
ghtoken: env.GH_TOKEN || env.GITHUB_TOKEN || false,
|
9
|
+
ghrepo: env.GH_REPO || env.GITHUB_REPO || false,
|
10
|
+
headless: env.PUPPETEER_HEADLESS || "1",
|
11
|
+
debug: (env.DEBUG && /^semantic-release:(\*|whmcs)$/.test(env.DEBUG)) || false,
|
12
|
+
useCookieExtension: env.USE_COOKIE_EXTENSION || true,
|
13
|
+
};
|
14
|
+
};
|
@@ -2,6 +2,7 @@ import puppet from "./puppet.js";
|
|
2
2
|
import debugConfig from "debug";
|
3
3
|
import { fileURLToPath } from "node:url";
|
4
4
|
import { robustType, wait, waitForSubmitResult, loginAndNavigate, clickAndWaitForResult } from "./puppet-utils.js";
|
5
|
+
import { safeClose } from "./puppet-utils.js";
|
5
6
|
const debug = debugConfig("semantic-release:whmcs");
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
7
8
|
|
@@ -12,10 +13,9 @@ export default async (pluginConfig, context) => {
|
|
12
13
|
const sep = "+++++++++++++++++++++++++++++++++++++++++++++++++++";
|
13
14
|
const out = `\n${sep}\n${__filename}\n${sep}\n`;
|
14
15
|
|
15
|
-
let page, püppi;
|
16
|
-
const { productid, urlbase, gotoOpts, selectorOpts, minversion } = (context && context.config) || {};
|
17
|
-
|
16
|
+
let page, püppi, urlbase, productid, gotoOpts, selectorOpts, minversion;
|
18
17
|
debug(out);
|
18
|
+
let success = false;
|
19
19
|
try {
|
20
20
|
// Login and navigate to compatibility page
|
21
21
|
({ page, püppi } = await loginAndNavigate(
|
@@ -24,7 +24,7 @@ export default async (pluginConfig, context) => {
|
|
24
24
|
(p) => `${p.config.urlbase}/product/${p.config.productid}/edit#compatibility`,
|
25
25
|
undefined
|
26
26
|
));
|
27
|
-
|
27
|
+
({ urlbase, productid, gotoOpts, selectorOpts, minversion } = püppi.config);
|
28
28
|
const url = `${urlbase}/product/${productid}/edit#compatibility`;
|
29
29
|
debug("product page loaded at %s", url);
|
30
30
|
|
@@ -69,20 +69,30 @@ export default async (pluginConfig, context) => {
|
|
69
69
|
const result = await waitForSubmitResult(page, { timeout: gotoOpts.timeout || 10000 });
|
70
70
|
if (result === "error") {
|
71
71
|
debug("Compatibility update failed: error alert shown.");
|
72
|
-
|
73
|
-
return false;
|
72
|
+
success = false;
|
74
73
|
} else if (result === "success") {
|
75
74
|
debug("Compatibility update succeeded.");
|
75
|
+
success = true;
|
76
76
|
} else {
|
77
77
|
debug("No success or error alert appeared after submit.");
|
78
78
|
}
|
79
79
|
} catch (error) {
|
80
80
|
debug("Updating whmcs compatibility list failed.", error.message);
|
81
|
-
|
82
|
-
|
81
|
+
success = false;
|
82
|
+
} finally {
|
83
|
+
if (page) {
|
84
|
+
await safeClose(page);
|
85
|
+
}
|
83
86
|
}
|
84
87
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
+
if (success) {
|
89
|
+
debug("Updating whmcs compatibility list succeeded.");
|
90
|
+
return {
|
91
|
+
name: "WHMCS Marketplace Compatibility Update",
|
92
|
+
url: `${urlbase}/product/${productid}/edit#compatibility`,
|
93
|
+
};
|
94
|
+
} else {
|
95
|
+
debug("Updating whmcs compatibility list failed.");
|
96
|
+
return false;
|
97
|
+
}
|
88
98
|
};
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@hexonet/semantic-release-whmcs",
|
3
3
|
"description": "`semantic-release` plugin for auto-publishing on WHMCS marketplace",
|
4
|
-
"version": "5.1.
|
4
|
+
"version": "5.1.4",
|
5
5
|
"private": false,
|
6
6
|
"type": "module",
|
7
7
|
"publishConfig": {
|
@@ -40,7 +40,7 @@
|
|
40
40
|
"nodeArguments": [
|
41
41
|
"--no-warnings"
|
42
42
|
],
|
43
|
-
"timeout": "
|
43
|
+
"timeout": "5m"
|
44
44
|
},
|
45
45
|
"prettier": {
|
46
46
|
"printWidth": 120,
|
@@ -77,7 +77,7 @@
|
|
77
77
|
"ava": "6.4.1",
|
78
78
|
"c8": "^10.0.0",
|
79
79
|
"prettier": "^3.0.0",
|
80
|
-
"semantic-release": "^24.
|
80
|
+
"semantic-release": "^24.2.7",
|
81
81
|
"semantic-release-teams-notify-plugin": "github:centralnicgroup-opensource/rtldev-middleware-semantic-release-notify-plugin",
|
82
82
|
"stream-buffers": "^3.0.2"
|
83
83
|
},
|