@hexonet/semantic-release-whmcs 5.1.22 → 5.2.0

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.
@@ -1,5 +1,6 @@
1
1
  name: Dependabot auto-merge & tests
2
2
  on:
3
+ workflow_call:
3
4
  pull_request:
4
5
  types:
5
6
  - opened
package/HISTORY.md CHANGED
@@ -1,3 +1,18 @@
1
+ # [5.2.0](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/compare/v5.1.22...v5.2.0) (2026-01-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **deps:** bump puppeteer from 24.30.0 to 24.31.0 ([7d2ff8a](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/commit/7d2ff8a2ead33c606f76e72303a92ed2fda05622))
7
+ * **deps:** bump puppeteer from 24.31.0 to 24.33.0 ([e05fd23](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/commit/e05fd2336697239e1766f0617e08c8c62fc83040))
8
+ * **deps:** bump puppeteer from 24.33.0 to 24.33.1 ([cfbb3ad](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/commit/cfbb3adb2310cbb04e9c4a48520e05f74cc996bf))
9
+ * **deps:** bump puppeteer from 24.33.1 to 24.34.0 ([aff5059](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/commit/aff5059079c0c95cec7c513c2d12a0a79dd45c03))
10
+
11
+
12
+ ### Features
13
+
14
+ * **index.js:** add prepare step to install Chromium/Chrome OS dependencies for Puppeteer ([7e61220](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/commit/7e61220646f4e3bcf68d300370a47668955b990b))
15
+
1
16
  ## [5.1.22](https://github.com/centralnicgroup-opensource/rtldev-middleware-semantic-release-whmcs/compare/v5.1.21...v5.1.22) (2025-11-13)
2
17
 
3
18
 
package/README.md CHANGED
@@ -11,6 +11,7 @@
11
11
 
12
12
  | Step | Description |
13
13
  | ---- | ----------- |
14
+ | `prepare` | Install OS dependencies required by Puppeteer and Chromium/Chrome (Debian/Ubuntu only) if `osDepsCommand` is configured. |
14
15
  | `verifyConditions` | Verify the presence and the validity of the authentication credentials (set via [environment variables](#environment-variables)) and the product id option configuration. |
15
16
  | `publish` | Publish product/module version to [WHMCS Marketplace](https://marketplace.whmcs.com) including changelog notes. |
16
17
 
@@ -27,7 +28,7 @@ FYI: This module is ESM ready!
27
28
  ### Requirements
28
29
 
29
30
  * Installed nodejs/npm. We suggest using [nvm](https://github.com/creationix/nvm).
30
- * Installed browser and related OS software dependencies e.g. `sudo npx puppeteer browsers install chrome --install-deps`. It requires root privileges that's why it is sudo'ed. Check the puppeteer docs, for other browsers if you're interested. Note: it may require `sudo apt-get update` first to work.
31
+ * **OS Dependencies**: On Debian/Ubuntu systems, the `prepare` step can run a custom command (e.g. `puppeteer browsers install chrome --install-deps`) to install required packages on demand. This is configured via `osDepsCommand`.
31
32
  * Using [semantic-release](https://github.com/semantic-release/semantic-release) in your CI/CD process
32
33
 
33
34
  ### Install
@@ -38,14 +39,27 @@ FYI: This module is ESM ready!
38
39
 
39
40
  ### Configuration
40
41
 
41
- The plugin can be loaded in the [**semantic-release** configuration file](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#configuration). Currently no configuration options are available.
42
+ The plugin can be loaded in the [**semantic-release** configuration file](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#configuration). The `prepare` step handles OS dependencies if `osDepsCommand` is provided (requires `sudo` access on Debian/Ubuntu for system packages).
42
43
 
43
44
  ```json
44
45
  {
45
46
  "plugins": [
46
47
  "@semantic-release/commit-analyzer",
47
48
  "@semantic-release/release-notes-generator",
48
- "@hexonet/semantic-release-whmcs"
49
+ [
50
+ "@hexonet/semantic-release-whmcs",
51
+ {
52
+ "osDepsCommand": [
53
+ "pnpm",
54
+ "dlx",
55
+ "puppeteer",
56
+ "browsers",
57
+ "install",
58
+ "chrome",
59
+ "--install-deps"
60
+ ]
61
+ }
62
+ ]
49
63
  ]
50
64
  }
51
65
  ```
@@ -81,7 +95,10 @@ That said, before you can use this module for publishing new product/module vers
81
95
 
82
96
  ### Options
83
97
 
84
- None available yet.
98
+ | Option | Type | Description |
99
+ | --- | --- | --- |
100
+ | `skipOsDeps` | Boolean | **Optional.** Skip OS dependency installation in the prepare step. Default: `false`. Useful if you already have dependencies installed or prefer to manage them yourself. |
101
+ | `osDepsCommand` | string[] | **Optional.** Command to install dependencies (e.g. `["pnpm", "dlx", "puppeteer", "browsers", "install", "chrome", "--install-deps"]`). <br><br> **Note:** The command is automatically executed with `sudo` and the current `PATH` preserved (via `/bin/bash -c "sudo env PATH=$PATH ..."`), so you do not need to add `sudo` yourself. |
85
102
 
86
103
  ### Routines
87
104
 
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import verifyWHMCS from "./lib/verify.js";
2
+ import prepareWHMCS from "./lib/prepare.js";
2
3
  import publishWHMCS from "./lib/publish.js";
3
4
  import deleteVersion from "./lib/delete-marketplace-version.js";
4
5
  import setCompatibleVersions from "./lib/set-compatible-versions.js";
@@ -8,6 +9,15 @@ import marketplaceVersions from "./lib/scrape-marketplace-versions.js";
8
9
  let verified;
9
10
  let whmcsPublishResult = null;
10
11
 
12
+ /**
13
+ * Called by semantic-release during the prepare step
14
+ * @param {*} pluginConfig The semantic-release plugin config
15
+ * @param {*} context The context provided by semantic-release
16
+ */
17
+ async function prepare(pluginConfig, context) {
18
+ await prepareWHMCS(pluginConfig, context);
19
+ }
20
+
11
21
  /**
12
22
  * Called by semantic-release during the verify step
13
23
  * @param {*} pluginConfig The semantic-release plugin config
@@ -144,6 +154,7 @@ async function fail(pluginConfig, context) {
144
154
  }
145
155
 
146
156
  export default {
157
+ prepare,
147
158
  verifyConditions,
148
159
  publish,
149
160
  //success,
package/lib/prepare.js ADDED
@@ -0,0 +1,110 @@
1
+ import { spawn } from "child_process";
2
+ import debugConfig from "debug";
3
+
4
+ const debug = debugConfig("semantic-release:whmcs");
5
+
6
+ // Exported for testing/debugging
7
+ export const DEBIAN_MARKER = "/etc/debian_version";
8
+
9
+ let spawnImplementation = spawn;
10
+
11
+ /**
12
+ * Test-only hook to override the child_process.spawn implementation.
13
+ * @param {(cmd: string, args?: string[], options?: object) => NodeJS.Process} implementation
14
+ */
15
+ export function __setSpawnImplementation(implementation) {
16
+ spawnImplementation = implementation;
17
+ }
18
+
19
+ /**
20
+ * Test-only hook to reset spawn back to the native implementation.
21
+ */
22
+ export function __resetSpawnImplementation() {
23
+ spawnImplementation = spawn;
24
+ }
25
+
26
+ /**
27
+ * Spawn a child process and return a promise
28
+ * @param {string} command The command to run
29
+ * @param {string[]} args The arguments to pass to the command
30
+ * @param {object} options Options to pass to spawn
31
+ * @returns {Promise<number>} The exit code of the process
32
+ */
33
+ function spawnPromise(command, args, options = {}) {
34
+ return new Promise((resolve, reject) => {
35
+ const child = spawnImplementation(command, args, { stdio: "inherit", ...options });
36
+ child.on("exit", (code) => {
37
+ if (code === 0) resolve(code);
38
+ else reject(new Error(`Process exited with code ${code}`));
39
+ });
40
+ child.on("error", reject);
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Check if the system is Debian-like
46
+ * @returns {Promise<boolean>}
47
+ */
48
+ async function isDebianLike() {
49
+ return new Promise((resolve) => {
50
+ const child = spawnImplementation("test", ["-f", DEBIAN_MARKER], { stdio: "ignore" });
51
+ child.on("exit", (code) => resolve(code === 0));
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Called by semantic-release during the prepare step
57
+ * @param {*} pluginConfig The semantic-release plugin config
58
+ * @param {*} context The context provided by semantic-release
59
+ */
60
+ export default async (pluginConfig = {}, context = {}) => {
61
+ const { nextRelease, lastRelease } = context;
62
+
63
+ // Only run if there is a new version
64
+ if (lastRelease?.version && nextRelease?.version === lastRelease.version) {
65
+ debug("Skipping prepare step: No new version detected");
66
+ return;
67
+ }
68
+
69
+ // Check if OS dependency installation is disabled
70
+ if (pluginConfig.skipOsDeps === true) {
71
+ debug("OS dependency installation skipped (skipOsDeps=true)");
72
+ console.log("ℹ️ Skipping OS dependency installation");
73
+ return;
74
+ }
75
+
76
+ try {
77
+ debug("Installing Chromium/Chrome OS dependencies for puppeteer...");
78
+ if (await isDebianLike()) {
79
+ try {
80
+ debug("Detected Debian/Ubuntu system, installing google-chrome-stable via apt-get");
81
+
82
+ // Update package lists and install Google Chrome stable
83
+ await spawnPromise("sudo", ["apt-get", "update"]);
84
+ await spawnPromise("sudo", ["apt-get", "install", "-y", "google-chrome-stable"]);
85
+ debug("Chromium/Chrome dependencies installed successfully");
86
+
87
+ // Set Chrome executable path for Puppeteer if configured
88
+ const chromePath = "/usr/bin/google-chrome-stable";
89
+ if (!process.env.PUPPETEER_EXECUTABLE_PATH) {
90
+ process.env.PUPPETEER_EXECUTABLE_PATH = chromePath;
91
+ debug("Set PUPPETEER_EXECUTABLE_PATH to %s", chromePath);
92
+ } else {
93
+ debug("PUPPETEER_EXECUTABLE_PATH already set to %s", process.env.PUPPETEER_EXECUTABLE_PATH);
94
+ }
95
+ } catch (error) {
96
+ debug("Warning: Could not install google-chrome-stable via apt-get: %s", error.message);
97
+ console.warn(
98
+ `⚠️ Warning: Failed to install some OS dependencies: ${error.message}\n` +
99
+ "⚠️ Some dependencies may need to be installed manually or puppeteer may fall back to downloading Chromium"
100
+ );
101
+ }
102
+ } else {
103
+ debug("Debian/Ubuntu system not detected, skipping OS dependency installation");
104
+ console.log("ℹ️ Running on non-Debian system. Puppeteer will download Chromium if needed.");
105
+ }
106
+ } catch (error) {
107
+ debug("Prepare step failed: %s", error.message);
108
+ throw error;
109
+ }
110
+ };
@@ -32,7 +32,7 @@ export async function loginAndNavigate(puppet, context, urlBuilder, gotoOpts) {
32
32
  }
33
33
  const { page } = püppi.config;
34
34
  const url = typeof urlBuilder === "function" ? urlBuilder(püppi) : urlBuilder;
35
- const navOpts = gotoOpts || { waitUntil: ["load", "domcontentloaded"], timeout: 10000 };
35
+ const navOpts = gotoOpts || püppi.config.gotoOpts || { waitUntil: ["load", "domcontentloaded"], timeout: 10000 };
36
36
  debug && debug(`Navigating to: ${url} with options: ${JSON.stringify(navOpts)}`);
37
37
  await page.goto(url, navOpts);
38
38
  debug && debug(`Navigation to ${url} complete.`);
package/lib/puppet.js CHANGED
@@ -16,14 +16,14 @@ export default async (context) => {
16
16
  // logger: logger,
17
17
  gotoOpts: {
18
18
  waitUntil: ["load", "domcontentloaded"],
19
- timeout: 3 * 1000, // 3 seconds
19
+ timeout: 30 * 1000, // increase to 30 seconds to handle slower loads
20
20
  },
21
21
  navOpts: {
22
22
  waitUntil: ["networkidle0"],
23
- timeout: 6 * 1000, // 6 seconds
23
+ timeout: 30 * 1000, // increase to 30 seconds to handle slower navigation
24
24
  },
25
25
  selectorOpts: {
26
- timeout: 6 * 1000, // 6 seconds
26
+ timeout: 15 * 1000, // increase to 15 seconds for selector waits
27
27
  },
28
28
  logger: context.logger,
29
29
  };
@@ -74,11 +74,13 @@ export default async (context) => {
74
74
  }
75
75
 
76
76
  async function login() {
77
- const { page, login, password, productid, gotoOpts, urlbase, selectorOpts } = config;
77
+ const { page, login, password, productid, gotoOpts, urlbase, selectorOpts, keepBrowserOpenOnError } = config;
78
78
  const submitSelector = 'div.login-leftcol form button[type="submit"]';
79
79
  try {
80
80
  await page.goto(`${urlbase}/user/login`, gotoOpts);
81
81
  debug("login form loaded at %s", `${urlbase}/user/login`);
82
+ // Give cookie banners/extensions a short window to run before interacting
83
+ await wait(2000);
82
84
  // Wait for login form
83
85
  await page.waitForSelector("#email", selectorOpts);
84
86
  await page.waitForSelector("#password", selectorOpts);
@@ -103,7 +105,12 @@ export default async (context) => {
103
105
  debug("Login failed: no navigation, no selector, and no error alert after submit");
104
106
  }
105
107
  // await page.screenshot({ path: `login-failed-no-redirect-or-selector.png` });
106
- await safeClose(page);
108
+ if (keepBrowserOpenOnError) {
109
+ debug("Keeping browser open for debugging after login failure.");
110
+ await wait(60 * 1000);
111
+ } else {
112
+ await safeClose(page);
113
+ }
107
114
  return false;
108
115
  }
109
116
  debug("WHMCS Marketplace login succeeded (redirected: %s, selectorFound: %s)", redirected, selectorFound);
@@ -114,7 +121,12 @@ export default async (context) => {
114
121
  } catch (e) {
115
122
  debug("Screenshot failed", e.message);
116
123
  }
117
- await safeClose(page);
124
+ if (keepBrowserOpenOnError) {
125
+ debug("Keeping browser open for debugging after login error.");
126
+ await wait(60 * 1000);
127
+ } else {
128
+ await safeClose(page);
129
+ }
118
130
  return false;
119
131
  }
120
132
 
@@ -122,7 +134,12 @@ export default async (context) => {
122
134
  let tmp = productid;
123
135
  if (!tmp || !/^[0-9]+$/.test(productid) || !parseInt(productid, 10)) {
124
136
  debug("No or invalid WHMCS Marketplace Product ID provided.");
125
- await safeClose(page);
137
+ if (keepBrowserOpenOnError) {
138
+ debug("Keeping browser open for debugging due to invalid product id.");
139
+ await wait(60 * 1000);
140
+ } else {
141
+ await safeClose(page);
142
+ }
126
143
  return false;
127
144
  }
128
145
  tmp = tmp.replace(/(.)/g, "$&\u200E");
@@ -10,5 +10,6 @@ export default (context) => {
10
10
  headless: env.PUPPETEER_HEADLESS || "1",
11
11
  debug: (env.DEBUG && /^semantic-release:(\*|whmcs)$/.test(env.DEBUG)) || false,
12
12
  useCookieExtension: env.USE_COOKIE_EXTENSION || true,
13
+ keepBrowserOpenOnError: env.PUPPETEER_KEEP_OPEN === "1" || env.PUPPETEER_KEEP_OPEN === "true" || false,
13
14
  };
14
15
  };
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.22",
4
+ "version": "5.2.0",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "publishConfig": {
@@ -72,20 +72,20 @@
72
72
  },
73
73
  "devDependencies": {
74
74
  "@semantic-release/changelog": "^6.0.3",
75
- "@semantic-release/exec": "^7.0.3",
75
+ "@semantic-release/exec": "^7.1.0",
76
76
  "@semantic-release/git": "^10.0.1",
77
77
  "ava": "6.4.1",
78
- "c8": "^10.0.0",
79
- "prettier": "^3.0.0",
80
- "semantic-release": "^25.0.1",
78
+ "c8": "^10.1.3",
79
+ "prettier": "^3.7.4",
80
+ "semantic-release": "^25.0.2",
81
81
  "semantic-release-teams-notify-plugin": "github:centralnicgroup-opensource/rtldev-middleware-semantic-release-notify-plugin",
82
- "stream-buffers": "^3.0.2"
82
+ "stream-buffers": "^3.0.3"
83
83
  },
84
84
  "dependencies": {
85
- "@octokit/rest": "^22.0.0",
85
+ "@octokit/rest": "^22.0.1",
86
86
  "@semantic-release/error": "^4.0.0",
87
87
  "aggregate-error": "^5.0.0",
88
- "debug": "^4.3.4",
88
+ "debug": "^4.4.3",
89
89
  "puppeteer": ">=23.4.0",
90
90
  "yargs": "^18.0.0"
91
91
  }
@@ -0,0 +1,2 @@
1
+ onlyBuiltDependencies:
2
+ - puppeteer