@hexonet/semantic-release-whmcs 4.0.2 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,105 +1,86 @@
1
1
  const debug = require('debug')('semantic-release:whmcs')
2
- const resolveConfig = require('./resolve-config')
3
2
  const puppet = require('./puppet')
3
+ const path = require('path')
4
4
 
5
5
  /**
6
6
  * A method to publish the module update on whmcs market place
7
7
  */
8
8
  module.exports = async (pluginConfig, context) => {
9
- const {
10
- version,
11
- logger
12
- } = context
9
+ // if (!context.logger) {
10
+ // context.logger = console;
11
+ // }
13
12
 
13
+ const sep = '+++++++++++++++++++++++++++++++++++++++++++++++++++'
14
+ const out = `\n${sep}\n${path.basename(__filename)}\n${sep}\n`
15
+
16
+ const { version } = context
14
17
  if (!version || !version.length) {
15
- debug('deleting product version failed. No input data available.')
16
- logger.error('WHMCS Marketplace deleting product version failed. No input data available.')
18
+ debug(`${out}Deleting product version failed. No input data available.`)
17
19
  return false
18
20
  }
19
21
 
20
- let success = true
21
- const { whmcsLOGIN, whmcsPASSWORD, whmcsPRODUCTID, withHEAD, DEBUG } = resolveConfig(context)
22
- const { gotoOpts, navOpts, selectorOpts, page } = await puppet(withHEAD, DEBUG, logger)
23
-
24
- debug(`WHMCS Marketplace Product ID: ${whmcsPRODUCTID}`)
25
- logger.log(`WHMCS Marketplace Product ID: ${whmcsPRODUCTID}`)
22
+ debug(`${out}Delete Version: ${version}`)
26
23
 
27
- debug(`Delete Version: ${version}`)
28
- logger.log(`Delete Version: ${version}`)
24
+ const püppi = await puppet(context)
25
+ const result = await püppi.login()
26
+ if (!result) {
27
+ return result
28
+ }
29
+ const { page, navOpts, gotoOpts, selectorOpts, productid, urlbase } =
30
+ püppi.config
29
31
 
30
32
  // strip markdown links from notes as not allowed to keep
31
- const wmbase = 'https://marketplace.whmcs.com'
32
- let url = `${wmbase}/user/login`
33
-
34
33
  try {
34
+ // navigate to product administration
35
+ const url = `${urlbase}/product/${productid}/edit#versions`
35
36
  await page.goto(url, gotoOpts)
36
- // do login
37
- const selector = 'div.login-leftcol form button[type="submit"]'
38
- await page.waitForSelector(selector, selectorOpts)
39
- debug('login form loaded at %s', url)
40
- await page.type('#email', whmcsLOGIN)
41
- await page.type('#password', whmcsPASSWORD)
42
- debug('WHMCS Marketplace credentials entered')
43
- const nav = page.waitForNavigation(navOpts)
44
- await page.hover(selector)
45
- await page.click(selector)
46
- await nav
47
- debug('Login form successfully submitted.')
48
- logger.log('WHMCS Marketplace Login Form successfully submitted.')
49
- } catch (error) {
50
- debug('WHMCS Marketplace login failed.', error.message)
51
- logger.error('WHMCS Marketplace login failed.', error.message)
52
- success = false
53
- }
54
- if (success) {
55
- try {
56
- if (!parseInt(whmcsPRODUCTID, 10)) {
57
- return false
58
- }
59
- // open versions tab
60
- url = `${wmbase}/product/${whmcsPRODUCTID}/edit#versions`
61
- await page.goto(url, gotoOpts)
62
- debug('product page loaded at %s', url)
37
+ debug('product page loaded at %s', url)
38
+ // open versions tab (instead of doing it via url)
39
+ // let selector = '#nav-tabs li a[href="#versions"]';
40
+ // await page.waitForSelector(selector, selectorOpts);
41
+ // await page.click(selector);
63
42
 
64
- // delete version
65
- const xpath = `//td[contains(., "Version ${version}")]/following-sibling::td/a[contains(@class, "btn-styled-red")]`
66
- await page.waitForXPath(xpath, selectorOpts)
43
+ // delete version
44
+ // xpath improvements / changes with v16.1.0
45
+ // -> https://github.com/puppeteer/puppeteer/pull/8730
46
+ // https://github.com/puppeteer/puppeteer/blob/d1681ec06b7c3db4b51c20b17b3339f852efbd4d/test/src/queryhandler.spec.ts
47
+ let elements = []
48
+ do {
49
+ const xpath = `xpath/.//td[contains(., "Version ${version}")]/following-sibling::td/a[contains(@class, "btn-styled-red")]`
50
+ await page.waitForSelector(xpath, selectorOpts)
67
51
  debug('XPath found.')
68
- let nav = page.waitForNavigation(navOpts)
69
- /* istanbul ignore next */
70
- const elements = await page.$x(xpath)
71
- debug('Delete Button - click.')
72
- await elements[0].hover()
73
- await elements[0].click()
74
- debug('Delete Button - clicked.')
75
- await nav
76
- debug('Navigation finished.')
77
-
78
- // confirm deletion
79
- const selector = 'div.listing-edit-container form button[type="submit"]'
80
- await page.waitForSelector(selector, selectorOpts)
81
- debug('confirmation form loaded at %s', url)
82
- nav = page.waitForNavigation(navOpts)
83
- await page.hover(selector)
84
- await page.click(selector)
85
- await nav
86
-
87
- debug('deleting product version succeeded.')
88
- logger.log('WHMCS Marketplace deleting product version succeeded.')
89
- success = true
90
- } catch (error) {
91
- debug('deleting product version failed.', error.message)
92
- logger.error('WHMCS Marketplace deleting product version failed.', error.message)
93
- success = false
52
+ const nav = page.waitForNavigation(navOpts)
53
+ elements = await page.$$(xpath)
54
+ if (elements.length) {
55
+ debug('Delete Button - click.')
56
+ await elements[0].hover()
57
+ await elements[0].click()
58
+ debug('Delete Button - clicked.')
59
+ await nav
60
+ debug('Navigation finished.')
61
+ // confirm deletion
62
+ const selector = 'button.btn-styled-red'
63
+ await page.waitForSelector(selector, selectorOpts)
64
+ debug('deletion confirmation button available')
65
+ debug('click confirmation button')
66
+ await page.clickAndNavigate(selector)
67
+ debug('clicked confirmation button')
68
+ await nav
69
+ debug('WHMCS Marketplace deleting product version succeeded.')
70
+ }
71
+ } while (elements.length)
72
+ } catch (error) {
73
+ // while loop and having all versions deleted
74
+ if (!/waiting for selector `.\/\//i.test(error.message)) {
75
+ debug('Deleting product version failed.', error.message)
76
+ await page.browser().close()
77
+ return false
94
78
  }
95
79
  }
96
80
 
97
81
  await page.browser().close()
98
- if (!success) {
99
- return false
100
- }
101
82
  return {
102
83
  name: 'WHMCS Marketplace Product Version',
103
- url: `${wmbase}/product/${whmcsPRODUCTID}`
84
+ url: `${urlbase}/product/${productid}`
104
85
  }
105
86
  }
@@ -9,27 +9,25 @@ const GitHub = require('github-api')
9
9
  * A method to get releases from github repository
10
10
  */
11
11
  module.exports = async (pluginConfig, context) => {
12
- const {
13
- logger
14
- } = context
15
-
16
12
  debug('Getting releases from GitHub')
17
- logger.log('Getting releases from GitHub')
18
13
 
19
- const { githubREPO, githubTOKEN } = resolveConfig(context)
14
+ const { ghrepo, ghtoken } = resolveConfig(context)
20
15
 
21
16
  const gh = new GitHub({
22
- token: githubTOKEN
17
+ token: ghtoken
23
18
  })
24
- if (githubREPO) { // optional by default false
25
- const repo = githubREPO.split('/')
19
+ if (ghrepo) {
20
+ // optional by default false
21
+ const repo = ghrepo.split('/')
26
22
  const githubReleases = await gh.getRepo(repo[0], repo[1]).listReleases()
27
23
  if (githubReleases.status === 200) {
28
- githubReleases.data.forEach(r => logger.log(`Detected GitHub release ${r.name.substring(1)}`))
24
+ githubReleases.data.forEach((r) =>
25
+ debug(`Detected GitHub release ${r.name.substring(1)}`)
26
+ )
29
27
  githubReleases.data.reverse()
30
28
  return githubReleases.data
31
29
  }
32
- logger.log('Failed to get releases from GitHub')
30
+ debug('Failed to get releases from GitHub')
33
31
  }
34
32
  return false
35
33
  }
package/lib/publish.js CHANGED
@@ -1,136 +1,95 @@
1
- const debug = require('debug')('semantic-release:whmcs')
2
- const resolveConfig = require('./resolve-config')
3
- const puppet = require('./puppet')
4
- const setCompatibleVersions = require('./set-compatible-versions')
1
+ const debug = require("debug")("semantic-release:whmcs");
2
+ const puppet = require("./puppet");
3
+ const setCompatibleVersions = require("./set-compatible-versions");
4
+ const path = require("path");
5
5
 
6
6
  /**
7
7
  * A method to publish the module update on whmcs market place
8
8
  */
9
9
  module.exports = async (pluginConfig, context) => {
10
+ const sep = "+++++++++++++++++++++++++++++++++++++++++++++++++++";
11
+ const out = `\n${sep}\n${path.basename(__filename)}\n${sep}\n`;
12
+
10
13
  const {
11
14
  nextRelease: { notes, version, releaseDate },
12
- logger
13
- } = context
14
-
15
+ } = context;
15
16
  if (!notes || !notes.length || !version || !version.length) {
16
- debug('publishing new product version failed. No input data available.')
17
- logger.error(
18
- 'WHMCS Marketplace publishing new product version failed. No input data available.'
19
- )
17
+ debug(
18
+ `${out}Publishing new product version failed. No input data available.`
19
+ );
20
+ return false;
20
21
  }
21
-
22
- let success = true
23
- const { whmcsLOGIN, whmcsPASSWORD, whmcsPRODUCTID, withHEAD, DEBUG } =
24
- resolveConfig(context)
25
- const { gotoOpts, navOpts, selectorOpts, page } = await puppet(withHEAD, DEBUG, logger)
26
-
27
- debug(`WHMCS Marketplace Product ID: ${whmcsPRODUCTID}`)
28
- logger.log(`WHMCS Marketplace Product ID: ${whmcsPRODUCTID}`)
29
-
30
- debug(`Release Version: ${version}`)
31
- logger.log(`Release Version: ${version}`)
32
- debug(`Notes: ${notes}`)
33
- logger.log(`Release Notes: ${notes}`)
34
-
35
22
  // strip markdown links from notes as not allowed to keep (taken from remove-markdown and cleaned up)
36
- const cleanedNotes = notes.replace(/\[([^[\]]*)\]\([^()]*\)/gm, '$1')
37
-
38
- const wmbase = 'https://marketplace.whmcs.com'
39
- let url = `${wmbase}/user/login`
23
+ const cleanedNotes = notes.replace(/\[([^[\]]*)\]\([^()]*\)/gm, "$1");
40
24
 
41
- try {
42
- await page.goto(url, gotoOpts)
43
- // do login
44
- const selector = 'div.login-leftcol form button[type="submit"]'
45
- await page.waitForSelector(selector, selectorOpts)
46
- debug('login form loaded at %s', url)
47
- await page.type('#email', whmcsLOGIN)
48
- await page.type('#password', whmcsPASSWORD)
49
- debug('WHMCS Marketplace credentials entered')
50
- const nav = page.waitForNavigation(navOpts)
51
- await page.hover(selector)
52
- await page.click(selector)
53
- await nav
54
- debug('Login form successfully submitted.')
55
- logger.log('WHMCS Marketplace Login Form successfully submitted.')
56
- } catch (error) {
57
- debug('WHMCS Marketplace login failed.', error.message)
58
- logger.error('WHMCS Marketplace login failed.', error.message)
59
- success = false
25
+ const püppi = await puppet(context);
26
+ const result = await püppi.login();
27
+ if (!result) {
28
+ return result;
60
29
  }
61
- if (success) {
62
- try {
63
- if (!parseInt(whmcsPRODUCTID, 10)) {
64
- return false
65
- }
66
- // add new version
67
- url = `${wmbase}/product/${whmcsPRODUCTID}/versions/new`
68
- await page.goto(url, gotoOpts)
69
- debug('product page loaded at %s', url)
70
- const selector = 'div.listing-edit-container form button[type="submit"]'
71
- await page.waitForSelector(selector, selectorOpts)
72
- debug('product page submit button selector found')
73
- /* istanbul ignore next */
74
- await page.$eval(
75
- '#version',
76
- (el, value) => {
77
- el.value = value
78
- },
79
- version
80
- )
81
- debug('form input for version finished.')
30
+ const { page, gotoOpts, selectorOpts, productid, urlbase } = püppi.config;
82
31
 
83
- // fill input type date with localized string
84
- // https://www.mattzeunert.com/2020/04/01/filling-out-a-date-input-with-puppeteer.html
85
- const date = releaseDate ? new Date(releaseDate) : new Date()
86
- /* istanbul ignore next */
87
- const dateString = await page.evaluate(
88
- (d) =>
89
- new Date(d).toLocaleDateString(navigator.language, {
90
- day: '2-digit',
91
- month: '2-digit',
92
- year: 'numeric'
93
- }),
94
- date.toISOString()
95
- )
96
- await page.type('#released_at', dateString)
32
+ debug(`Release Version: ${version}`);
33
+ debug(`Notes: ${notes}`);
97
34
 
98
- debug('form input for released_at finished.')
99
- /* istanbul ignore next */
100
- await page.$eval(
101
- '#description',
102
- (el, value) => {
103
- el.value = value
104
- },
105
- cleanedNotes
106
- )
107
- debug('form input for description finished.')
108
- const nav = page.waitForNavigation(navOpts)
109
- await page.hover(selector)
110
- await page.click(selector)
111
- await nav
35
+ try {
36
+ // add new version
37
+ const url = `${urlbase}/product/${productid}/versions/new`;
38
+ await page.goto(url, gotoOpts);
39
+ debug("product page loaded at %s", url);
40
+ const selector = 'div.listing-edit-container form button[type="submit"]';
41
+ await page.waitForSelector(selector, selectorOpts);
42
+ debug("product page submit button selector found");
43
+ /* istanbul ignore next */
44
+ await page.$eval(
45
+ "#version",
46
+ (el, value) => {
47
+ el.value = value;
48
+ },
49
+ version
50
+ );
51
+ debug("form input for version finished.");
112
52
 
113
- await setCompatibleVersions(pluginConfig, context)
53
+ // fill input type date with localized string
54
+ // https://www.mattzeunert.com/2020/04/01/filling-out-a-date-input-with-puppeteer.html
55
+ const date = releaseDate ? new Date(releaseDate) : new Date();
56
+ /* istanbul ignore next */
57
+ const dateString = await page.evaluate(
58
+ (d) =>
59
+ new Date(d).toLocaleDateString(navigator.language, {
60
+ day: "2-digit",
61
+ month: "2-digit",
62
+ year: "numeric",
63
+ }),
64
+ date.toISOString()
65
+ );
66
+ await page.enterAndType("#released_at", dateString);
114
67
 
115
- debug('publishing new product version succeeded.')
116
- logger.log('WHMCS Marketplace publishing new product version succeeded.')
117
- success = true
118
- } catch (error) {
119
- debug('publishing new product version failed.', error.message)
120
- logger.error(
121
- 'WHMCS Marketplace publishing new product version failed.',
122
- error.message
123
- )
124
- success = false
125
- }
126
- }
68
+ debug("form input for released_at finished.");
69
+ /* istanbul ignore next */
70
+ await page.$eval(
71
+ "#description",
72
+ (el, value) => {
73
+ el.value = value;
74
+ },
75
+ cleanedNotes
76
+ );
77
+ debug("form input for description finished.");
127
78
 
128
- await page.browser().close()
129
- if (!success) {
130
- return false
79
+ await page.clickAndNavigate(
80
+ 'div.listing-edit-container form button[type="submit"]'
81
+ );
82
+ await setCompatibleVersions(pluginConfig, context);
83
+ } catch (error) {
84
+ debug("Publishing new product version failed.", error.message);
85
+ await page.browser().close();
86
+ return false;
131
87
  }
88
+ debug("Publishing new product version succeeded.");
89
+ await page.browser().close();
90
+
132
91
  return {
133
- name: 'WHMCS Marketplace Product Version',
134
- url: `${wmbase}/product/${whmcsPRODUCTID}`
135
- }
136
- }
92
+ name: "WHMCS Marketplace Product Version",
93
+ url: `${urlbase}/product/${productid}`,
94
+ };
95
+ };
package/lib/puppet.js CHANGED
@@ -1,25 +1,31 @@
1
1
  const puppeteer = require('puppeteer')
2
+ const debug = require('debug')('semantic-release:whmcs')
3
+ const resolveConfig = require('./resolve-config')
2
4
 
3
- module.exports = async (withHEAD, DEBUG, logger) => {
4
- if (!logger) {
5
- logger = console
6
- }
7
- const gotoOpts = {
8
- waitUntil: ['load', 'domcontentloaded'],
9
- timeout: 240000
10
- }
11
- const navOpts = {
12
- waitUntil: 'networkidle0',
13
- timeout: 240000
14
- }
15
- const selectorOpts = {
16
- timeout: 10000
5
+ module.exports = async (context) => {
6
+ const cfg = {
7
+ urlbase: 'https://marketplace.whmcs.com',
8
+ ...resolveConfig(context),
9
+ // logger: logger,
10
+ gotoOpts: {
11
+ waitUntil: ['load', 'domcontentloaded'],
12
+ timeout: 240000
13
+ },
14
+ navOpts: {
15
+ waitUntil: ['networkidle0'],
16
+ timeout: 240000
17
+ },
18
+ selectorOpts: {
19
+ timeout: 10000
20
+ },
21
+ logger: context.logger
17
22
  }
18
23
 
19
24
  const browser = await puppeteer.launch({
20
- headless: !withHEAD,
25
+ headless: cfg.headless === '1',
21
26
  defaultViewport: null, // automatically full-sized
22
27
  args: [
28
+ '--disable-gpu',
23
29
  '--incognito',
24
30
  '--start-maximized',
25
31
  '--no-sandbox',
@@ -27,24 +33,21 @@ module.exports = async (withHEAD, DEBUG, logger) => {
27
33
  '--disable-infobars',
28
34
  '--ignore-certifcate-errors',
29
35
  '--ignore-certifcate-errors-spki-list',
30
- '--ignoreHTTPSErrors=true',
31
- '--user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"'
36
+ '--ignoreHTTPSErrors=true'
32
37
  ]
33
38
  })
39
+ const { logger } = cfg
34
40
  const [page] = await browser.pages()
35
-
36
- await page.setRequestInterception(true)
37
- page.on('request', request => {
38
- if (/^(image|stylesheet|other)$/i.test(request.resourceType())) {
39
- request.abort()
40
- } else {
41
- request.continue()
42
- }
41
+ await page.setExtraHTTPHeaders({
42
+ 'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8'
43
43
  })
44
+ await page.setUserAgent(
45
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
46
+ )
44
47
  await page.setJavaScriptEnabled(true)
45
48
 
46
- if (DEBUG) {
47
- page.on('console', msg => {
49
+ if (cfg.debug) {
50
+ page.on('console', (msg) => {
48
51
  for (let i = 0; i < msg.args().length; i++) {
49
52
  logger.log(`${i}: ${msg.args()[i]}`)
50
53
  }
@@ -52,11 +55,59 @@ module.exports = async (withHEAD, DEBUG, logger) => {
52
55
  }
53
56
 
54
57
  page.clickAndNavigate = async (selector) => {
58
+ const { page, navOpts } = this.config
55
59
  const nav = page.waitForNavigation(navOpts)
56
60
  await page.hover(selector)
57
61
  await page.click(selector)
58
62
  await nav
59
63
  }
60
64
 
61
- return { page, gotoOpts, navOpts, selectorOpts }
65
+ page.enterAndType = async (selector, value) => {
66
+ const { page, selectorOpts } = this.config
67
+ await page.waitForSelector(selector, selectorOpts)
68
+ await page.type(selector, value)
69
+ }
70
+
71
+ this.login = async () => {
72
+ const { page, login, password, productid, gotoOpts, urlbase } = this.config
73
+ const selector = 'div.login-leftcol form button[type="submit"]'
74
+ // do login
75
+ try {
76
+ await page.goto(`${urlbase}/user/login`, gotoOpts)
77
+ debug('login form loaded at %s', `${urlbase}/user/login`)
78
+ await page.enterAndType('#email', login)
79
+ await page.enterAndType('#password', password)
80
+ debug('WHMCS Marketplace credentials entered')
81
+ await page.clickAndNavigate(selector)
82
+ debug('WHMCS Marketplace login form submitted.')
83
+ } catch (error) {
84
+ debug(
85
+ 'WHMCS Marketplace login failed or Product ID missing',
86
+ error.message
87
+ )
88
+ await page.browser().close()
89
+ return false
90
+ }
91
+ debug('WHMCS Marketplace login succeeded.')
92
+
93
+ // access MP Product ID
94
+ let tmp = productid
95
+ if (!tmp || !/^[0-9]+$/.test(productid) || !parseInt(productid, 10)) {
96
+ debug('No or invalid WHMCS Marketplace Product ID provided.')
97
+ await page.browser().close()
98
+ return false
99
+ }
100
+
101
+ tmp = tmp.replace(/(.)/g, '$&\u200E')
102
+ debug(`WHMCS Marketplace Product ID: ${tmp}`)
103
+
104
+ return true
105
+ }
106
+
107
+ this.config = {
108
+ ...cfg,
109
+ page
110
+ }
111
+
112
+ return this
62
113
  }
@@ -1,10 +1,11 @@
1
1
  module.exports = ({ env }) => ({
2
- whmcsLOGIN: env.WHMCSMP_LOGIN || false,
3
- whmcsPASSWORD: env.WHMCSMP_PASSWORD || false,
4
- whmcsPRODUCTID: env.WHMCSMP_PRODUCTID || false,
5
- whmcsMINVERSION: env.WHMCSMP_MINVERSION || '7.10',
6
- githubTOKEN: env.GH_TOKEN || env.GITHUB_TOKEN || false,
7
- githubREPO: env.GH_REPO || env.GITHUB_REPO || false,
8
- withHEAD: env.WITH_HEAD || false,
9
- DEBUG: (env.DEBUG && /^semantic-release:(\*|whmcs)$/.test(env.DEBUG)) || false
2
+ login: env.WHMCSMP_LOGIN || false,
3
+ password: env.WHMCSMP_PASSWORD || false,
4
+ productid: env.WHMCSMP_PRODUCTID || false,
5
+ minversion: env.WHMCSMP_MINVERSION || '7.10',
6
+ ghtoken: env.GH_TOKEN || env.GITHUB_TOKEN || false,
7
+ ghrepo: env.GH_REPO || env.GITHUB_REPO || false,
8
+ headless: env.PUPPETEER_HEADLESS || '1',
9
+ debug:
10
+ (env.DEBUG && /^semantic-release:(\*|whmcs)$/.test(env.DEBUG)) || false
10
11
  })