@modernman00/shared-js-lib 1.2.49 → 1.2.61
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/AcctMgt/changePassword.js +23 -3
- package/AcctMgt/code.js +17 -2
- package/AcctMgt/forgot.js +19 -4
- package/AcctMgt/login.js +18 -3
- package/AcctMgt/processAll.js +5 -5
- package/AcctMgt/update.js +24 -2
- package/FormHelper.js +5 -5
- package/Http.js +6 -1
- package/config/version.php +1 -1
- package/package.json +25 -5
- package/release-history.md +22 -0
- package/.github/workflows/publish.yml +0 -53
- package/.github/workflows/release.yml +0 -40
- package/.github/workflows/test.yml +0 -32
- package/__tests__/handlers.test.js +0 -156
- package/babel.config.js +0 -3
- package/jest.config.js +0 -6
- package/release.sh +0 -161
- package/theAutoComplete.js +0 -93
|
@@ -2,6 +2,7 @@ import { loginSubmission } from './processAll.js';
|
|
|
2
2
|
import { id } from '../UtilityHtml.js';
|
|
3
3
|
import { bindEvent, showPassword } from '../Utility.js';
|
|
4
4
|
import { matchInput } from '../general.js';
|
|
5
|
+
import { showError } from '../ShowResponse.js';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -26,6 +27,7 @@ import { matchInput } from '../general.js';
|
|
|
26
27
|
* @param {string} [config.redirect] - URL to redirect to after successful password change.
|
|
27
28
|
* @param {string} [config.theme='bulma'] - UI theme identifier passed to `loginSubmission`.
|
|
28
29
|
* @param {number} [config.maxLength=50] - Maximum allowed length for password fields.
|
|
30
|
+
* @param {Object} [config.recaptchaV3=null] - reCAPTCHA v3 configuration ({ siteKey: 'xxx', action: 'CHANGE_PASSWORD' }).
|
|
29
31
|
*
|
|
30
32
|
* @example
|
|
31
33
|
* setupPasswordChange(); // Uses default IDs and routes
|
|
@@ -52,7 +54,8 @@ export const setupPasswordChange = ({
|
|
|
52
54
|
route,
|
|
53
55
|
redirect,
|
|
54
56
|
theme = 'bulma',
|
|
55
|
-
maxLength = 50
|
|
57
|
+
maxLength = 50,
|
|
58
|
+
recaptchaV3 = null // { siteKey: 'xxx', action: 'CHANGE_PASSWORD' }
|
|
56
59
|
} = {}) => {
|
|
57
60
|
// Accessibility & input attributes
|
|
58
61
|
const passwordInput = id(passwordId);
|
|
@@ -65,6 +68,8 @@ export const setupPasswordChange = ({
|
|
|
65
68
|
helperText.textContent =
|
|
66
69
|
'Password must be at least 8 characters long, contain at least one uppercase letter, one lowercase letter, one number, and one special character.';
|
|
67
70
|
|
|
71
|
+
|
|
72
|
+
|
|
68
73
|
// Length limit object
|
|
69
74
|
const lengthLimit = {
|
|
70
75
|
maxLength: {
|
|
@@ -74,9 +79,24 @@ export const setupPasswordChange = ({
|
|
|
74
79
|
};
|
|
75
80
|
|
|
76
81
|
// Submission handler
|
|
77
|
-
const handleSubmit = (e) => {
|
|
82
|
+
const handleSubmit = async (e) => {
|
|
78
83
|
e.preventDefault();
|
|
79
|
-
|
|
84
|
+
|
|
85
|
+
let recaptchaToken = null;
|
|
86
|
+
// Only run v3 if configured AND grecaptcha is available
|
|
87
|
+
if (recaptchaV3?.siteKey && window.grecaptcha && window.grecaptcha.enterprise) {
|
|
88
|
+
try {
|
|
89
|
+
recaptchaToken = await grecaptcha.enterprise.execute(
|
|
90
|
+
recaptchaV3.siteKey,
|
|
91
|
+
{ action: recaptchaV3.action || 'CHANGE_PASSWORD' });
|
|
92
|
+
} catch (err) {
|
|
93
|
+
showError(err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
loginSubmission(formId, route, redirect, theme, lengthLimit, [], recaptchaToken);
|
|
98
|
+
|
|
99
|
+
|
|
80
100
|
};
|
|
81
101
|
|
|
82
102
|
// Bind events
|
package/AcctMgt/code.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loginSubmission } from './processAll.js';
|
|
2
2
|
import { bindEvent } from '../Utility.js';
|
|
3
|
+
import { showError } from '../ShowResponse.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Creates a reusable form submission handler for code-based forms.
|
|
@@ -13,6 +14,7 @@ import { bindEvent } from '../Utility.js';
|
|
|
13
14
|
* @param {string} options.redirect - URL to redirect to after success.
|
|
14
15
|
* @param {string} [options.theme='bulma'] - UI theme identifier.
|
|
15
16
|
* @param {number} [options.maxLength=50] - Max input length.
|
|
17
|
+
* @param {Object} [options.recaptchaV3=null] - reCAPTCHA v3 configuration ({ siteKey: 'OUR_ENTERPRISE_SITE_KEYx', action: 'TOKEN' }).
|
|
16
18
|
*
|
|
17
19
|
* @example
|
|
18
20
|
* createCodeSubmitHandler({
|
|
@@ -30,11 +32,24 @@ export function createCodeSubmitHandler({
|
|
|
30
32
|
route,
|
|
31
33
|
redirect,
|
|
32
34
|
theme = 'bulma',
|
|
33
|
-
maxLength = 50
|
|
35
|
+
maxLength = 50,
|
|
36
|
+
recaptchaV3 = null // { siteKey: 'xxx', action: 'LOGIN' }
|
|
34
37
|
}) {
|
|
35
|
-
const handler = (e) => {
|
|
38
|
+
const handler = async (e) => {
|
|
36
39
|
e.preventDefault();
|
|
37
40
|
|
|
41
|
+
let recaptchaToken = null;
|
|
42
|
+
// Only run v3 if configured AND grecaptcha is available
|
|
43
|
+
if (recaptchaV3?.siteKey && window.grecaptcha && window.grecaptcha.enterprise) {
|
|
44
|
+
try {
|
|
45
|
+
recaptchaToken = await grecaptcha.enterprise.execute(
|
|
46
|
+
recaptchaV3.siteKey,
|
|
47
|
+
{ action: recaptchaV3.action || 'LOGIN' });
|
|
48
|
+
} catch (err) {
|
|
49
|
+
showError(err);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
const lengthLimit = {
|
|
39
54
|
maxLength: {
|
|
40
55
|
id: [inputId],
|
package/AcctMgt/forgot.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { loginSubmission} from './processAll.js';
|
|
2
|
-
import {bindEvent } from '../Utility.js';
|
|
1
|
+
import { loginSubmission } from './processAll.js';
|
|
2
|
+
import { bindEvent } from '../Utility.js';
|
|
3
|
+
import { showError } from '../ShowResponse.js';
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -19,6 +20,7 @@ import {bindEvent } from '../Utility.js';
|
|
|
19
20
|
* @param {string} options.redirect - URL to redirect to after success.
|
|
20
21
|
* @param {string} [options.theme='bulma'] - UI theme identifier.
|
|
21
22
|
* @param {number} [options.maxLength=50] - Max input length.
|
|
23
|
+
* @param {Object} [options.recaptchaV3=null] - reCAPTCHA v3 configuration ({ siteKey: 'OUR_ENTERPRISE_SITE_KEYx', action: 'FORGOT' }).
|
|
22
24
|
*
|
|
23
25
|
* @example
|
|
24
26
|
* createEmailSubmitHandler({
|
|
@@ -36,11 +38,24 @@ export function forgotSubmitHandler({
|
|
|
36
38
|
route,
|
|
37
39
|
redirect,
|
|
38
40
|
theme = 'bulma',
|
|
39
|
-
maxLength = 50
|
|
41
|
+
maxLength = 50,
|
|
42
|
+
recaptchaV3 = null // { siteKey: 'xxx', action: 'LOGIN' }
|
|
40
43
|
}) {
|
|
41
|
-
const handler = (e) => {
|
|
44
|
+
const handler = async (e) => {
|
|
42
45
|
e.preventDefault();
|
|
43
46
|
|
|
47
|
+
let recaptchaToken = null;
|
|
48
|
+
// Only run v3 if configured AND grecaptcha is available
|
|
49
|
+
if (recaptchaV3?.siteKey && window.grecaptcha && window.grecaptcha.enterprise) {
|
|
50
|
+
try {
|
|
51
|
+
recaptchaToken = await grecaptcha.enterprise.execute(
|
|
52
|
+
recaptchaV3.siteKey,
|
|
53
|
+
{ action: recaptchaV3.action || 'LOGIN' });
|
|
54
|
+
} catch (err) {
|
|
55
|
+
showError(err);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
44
59
|
const lengthLimit = {
|
|
45
60
|
maxLength: {
|
|
46
61
|
id: [inputId],
|
package/AcctMgt/login.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loginSubmission } from './processAll.js';
|
|
2
2
|
import { id} from '../UtilityHtml.js';
|
|
3
3
|
import { bindEvent, showPassword } from '../Utility.js';
|
|
4
|
+
import { showError } from '../ShowResponse.js';
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -23,6 +24,7 @@ import { bindEvent, showPassword } from '../Utility.js';
|
|
|
23
24
|
* @param {string} options.redirect - URL to redirect to after successful login.
|
|
24
25
|
* @param {string} [options.theme='bootstrap'] - UI theme identifier.
|
|
25
26
|
* @param {number[]} [options.maxLength=[30, 50]] - Max lengths for password and email respectively.
|
|
27
|
+
* @param {Object} [options.recaptchaV3=null] - reCAPTCHA v3 configuration ({ siteKey: 'OUR_ENTERPRISE_SITE_KEY', action: 'LOGIN' }).
|
|
26
28
|
*
|
|
27
29
|
* @example
|
|
28
30
|
* createAdminLoginHandler({
|
|
@@ -44,11 +46,24 @@ export function createAdminLoginHandler({
|
|
|
44
46
|
route,
|
|
45
47
|
redirect,
|
|
46
48
|
theme = 'bootstrap',
|
|
47
|
-
maxLength = [30, 50]
|
|
49
|
+
maxLength = [30, 50],
|
|
50
|
+
recaptchaV3 = null // { siteKey: 'xxx', action: 'LOGIN' }
|
|
48
51
|
}) {
|
|
49
|
-
const handler = (e) => {
|
|
52
|
+
const handler = async (e) => {
|
|
50
53
|
e.preventDefault();
|
|
51
54
|
|
|
55
|
+
let recaptchaToken = null;
|
|
56
|
+
// Only run v3 if configured AND grecaptcha is available
|
|
57
|
+
if (recaptchaV3?.siteKey && window.grecaptcha && window.grecaptcha.enterprise) {
|
|
58
|
+
try {
|
|
59
|
+
recaptchaToken = await grecaptcha.enterprise.execute(
|
|
60
|
+
recaptchaV3.siteKey,
|
|
61
|
+
{ action: recaptchaV3.action || 'LOGIN' } );
|
|
62
|
+
} catch (err) {
|
|
63
|
+
showError(err);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
const lengthLimit = {
|
|
53
68
|
maxLength: {
|
|
54
69
|
id: [passwordId, emailId],
|
|
@@ -56,7 +71,7 @@ export function createAdminLoginHandler({
|
|
|
56
71
|
}
|
|
57
72
|
};
|
|
58
73
|
|
|
59
|
-
loginSubmission(formId, route, redirect, theme, lengthLimit);
|
|
74
|
+
loginSubmission(formId, route, redirect, theme, lengthLimit, [], recaptchaToken);
|
|
60
75
|
};
|
|
61
76
|
|
|
62
77
|
bindEvent({ id: buttonId, handler });
|
package/AcctMgt/processAll.js
CHANGED
|
@@ -9,15 +9,15 @@ import FormHelper from '../FormHelper.js';
|
|
|
9
9
|
// block the setLoader div
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Handles the submission of the login form.
|
|
12
|
+
* Handles the submission of the login form and redirects the user.
|
|
13
13
|
* @param {string} formId - The ID of the form to submit.
|
|
14
14
|
* @param {string} loginURL - The URL to make the POST request to.
|
|
15
15
|
* @param {string} redirect - The URL to redirect the user to after the submission is complete.
|
|
16
16
|
* @param {string} [css=null] - The CSS class to add to the notification element if the submission is successful.
|
|
17
|
-
* @returns {void}
|
|
17
|
+
* @returns {void} yes
|
|
18
18
|
* @throws {Error} - If there is an error with the submission
|
|
19
19
|
*/
|
|
20
|
-
export const loginSubmission = async (formId, loginURL, redirect, css = null, lengthLimit = null
|
|
20
|
+
export const loginSubmission = async (formId, loginURL, redirect, css = null, lengthLimit = null, optionalFields = [], recaptchaToken = null
|
|
21
21
|
) => {
|
|
22
22
|
|
|
23
23
|
try {
|
|
@@ -39,7 +39,7 @@ export const loginSubmission = async (formId, loginURL, redirect, css = null, le
|
|
|
39
39
|
formData.realTimeCheckLen(lengthLimit.maxLength.id, lengthLimit.maxLength.max);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
formData.massValidate(
|
|
42
|
+
formData.massValidate(optionalFields, { email: 'email', password: 'password' });
|
|
43
43
|
|
|
44
44
|
if (formData.result === 1) {
|
|
45
45
|
|
|
@@ -51,7 +51,7 @@ export const loginSubmission = async (formId, loginURL, redirect, css = null, le
|
|
|
51
51
|
|
|
52
52
|
localStorage.setItem('redirect', redirect);
|
|
53
53
|
|
|
54
|
-
await postFormData(loginURL, formId, redirect, css);
|
|
54
|
+
await postFormData(loginURL, formId, redirect, css, recaptchaToken);
|
|
55
55
|
|
|
56
56
|
} else {
|
|
57
57
|
|
package/AcctMgt/update.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {id } from '../UtilityHtml.js';
|
|
1
|
+
import { id } from '../UtilityHtml.js';
|
|
2
2
|
import { bindEvent } from '../Utility.js';
|
|
3
|
-
import { msgException } from '../ShowResponse.js';
|
|
3
|
+
import { msgException, showError } from '../ShowResponse.js';
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
import { redirectAfterDelay, parseErrorResponse } from '../general.js';
|
|
6
6
|
/**
|
|
@@ -12,6 +12,8 @@ import { redirectAfterDelay, parseErrorResponse } from '../general.js';
|
|
|
12
12
|
* @param {string} options.buttonId - ID of the submit button.
|
|
13
13
|
* @param {string} options.route - API endpoint for submission.
|
|
14
14
|
* @param {string} options.redirect - URL to redirect to after success.
|
|
15
|
+
* @param {Object} [options.recaptchaV3=null] - reCAPTCHA v3 configuration ({ siteKey: 'OUR_ENTERPRISE_SITE_KEY', action: 'LOGIN' }).
|
|
16
|
+
*
|
|
15
17
|
*
|
|
16
18
|
* @example
|
|
17
19
|
* update({
|
|
@@ -26,10 +28,23 @@ export function update({
|
|
|
26
28
|
buttonId,
|
|
27
29
|
route,
|
|
28
30
|
redirect,
|
|
31
|
+
recaptchaV3 = null // { siteKey: 'xxx', action: 'UPDATE_DATA' }
|
|
29
32
|
}) {
|
|
30
33
|
const handler = async (e) => {
|
|
31
34
|
e.preventDefault();
|
|
32
35
|
|
|
36
|
+
let recaptchaToken = null;
|
|
37
|
+
// Only run v3 if configured AND grecaptcha is available
|
|
38
|
+
if (recaptchaV3?.siteKey && window.grecaptcha && window.grecaptcha.enterprise) {
|
|
39
|
+
try {
|
|
40
|
+
recaptchaToken = await grecaptcha.enterprise.execute(
|
|
41
|
+
recaptchaV3.siteKey,
|
|
42
|
+
{ action: recaptchaV3.action || 'LOGIN' });
|
|
43
|
+
} catch (err) {
|
|
44
|
+
showError(err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
const form = id(formId);
|
|
34
49
|
if (!form) {
|
|
35
50
|
msgException(`Form with ID "${formId}" not found.`);
|
|
@@ -39,6 +54,11 @@ export function update({
|
|
|
39
54
|
const formData = new FormData();
|
|
40
55
|
const inputs = form.querySelectorAll('[name]');
|
|
41
56
|
|
|
57
|
+
// if recaptcha token is provided, append it
|
|
58
|
+
if (recaptchaToken) {
|
|
59
|
+
formData.append('recaptchaTokenV3', recaptchaToken);
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
inputs.forEach(input => {
|
|
43
63
|
const name = input.name;
|
|
44
64
|
const original = input.getAttribute('data-original');
|
|
@@ -59,6 +79,7 @@ export function update({
|
|
|
59
79
|
const message = response.data.message;
|
|
60
80
|
|
|
61
81
|
const notificationId = id(`${formId}_notification`) || formId;
|
|
82
|
+
notificationId.style.display = 'block'
|
|
62
83
|
|
|
63
84
|
notificationId.scrollIntoView({ behavior: 'smooth' });
|
|
64
85
|
|
|
@@ -73,6 +94,7 @@ export function update({
|
|
|
73
94
|
console.log(error);
|
|
74
95
|
const ErrorMsg = parseErrorResponse(error);
|
|
75
96
|
const notificationId = id(`${formId}_notification`)
|
|
97
|
+
notificationId.style.display = 'block'
|
|
76
98
|
notificationId.innerHTML = `<li style="color:white; background-color:red">${ErrorMsg}</li>`;
|
|
77
99
|
}
|
|
78
100
|
};
|
package/FormHelper.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
export default class FormHelper {
|
|
3
3
|
constructor(data) {
|
|
4
|
-
if (!Array.isArray(data))
|
|
4
|
+
if (!Array.isArray(data)) throw new Error('data must be an array of form elements');
|
|
5
5
|
this.data = data;
|
|
6
6
|
this.error = [];
|
|
7
7
|
this.result = 0;
|
|
@@ -101,7 +101,7 @@ export default class FormHelper {
|
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
massValidate(optionalFields
|
|
104
|
+
massValidate(optionalFields, typeMap = {}) {
|
|
105
105
|
this.clearError(); // Reset errors
|
|
106
106
|
this.result = 0;
|
|
107
107
|
|
|
@@ -109,11 +109,11 @@ export default class FormHelper {
|
|
|
109
109
|
const { name, value, id, type, placeholder } = input;
|
|
110
110
|
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
// Skip non-input elements
|
|
113
113
|
if (
|
|
114
114
|
['submit', 'button', 'g-recaptcha-response', 'cancel'].includes(name) ||
|
|
115
115
|
['button', 'showPassword', 'token', 'g-recaptcha-response'].includes(id) ||
|
|
116
|
-
type === 'button' ||
|
|
116
|
+
type === 'button' || optionalFields.includes(id) ||
|
|
117
117
|
id === 'checkbox'
|
|
118
118
|
) continue;
|
|
119
119
|
|
|
@@ -123,7 +123,7 @@ export default class FormHelper {
|
|
|
123
123
|
const errorEl = this.id(`${id}_error`);
|
|
124
124
|
let val = value.trim();
|
|
125
125
|
|
|
126
|
-
// Handle optional fields
|
|
126
|
+
// Handle optional fields - If optional but empty → auto‑fill “Not Provided”
|
|
127
127
|
if (optionalFields.includes(id) && val === '') {
|
|
128
128
|
input.value = 'Not Provided';
|
|
129
129
|
continue;
|
package/Http.js
CHANGED
|
@@ -11,7 +11,7 @@ import { redirectAfterDelay, parseErrorResponse } from './general.js';
|
|
|
11
11
|
* @param {string|null} css - The CSS framework to use for notification styling (e.g., 'W3css', 'bulma').
|
|
12
12
|
NOTICE:::Make sure you set the notification id as the formId_notification
|
|
13
13
|
*/
|
|
14
|
-
export const postFormData = async (url, formId, redirect = null, css = null) => {
|
|
14
|
+
export const postFormData = async (url, formId, redirect = null, css = null, recaptchaToken = null) => {
|
|
15
15
|
|
|
16
16
|
let notificationForm = `${formId}_notification`;
|
|
17
17
|
const notificationId = id(notificationForm);
|
|
@@ -43,6 +43,11 @@ export const postFormData = async (url, formId, redirect = null, css = null) =>
|
|
|
43
43
|
|
|
44
44
|
let formEntries = new FormData(form);
|
|
45
45
|
|
|
46
|
+
// if recaptcha token is provided, append it for v3
|
|
47
|
+
if (recaptchaToken) {
|
|
48
|
+
formEntries.append('recaptchaTokenV3', recaptchaToken);
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
formEntries.delete('submit');
|
|
47
52
|
formEntries.delete('checkbox');
|
|
48
53
|
|
package/config/version.php
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modernman00/shared-js-lib",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.61",
|
|
4
4
|
"description": "Reusable JS utilities for numerous js problems",
|
|
5
5
|
"homepage": "https://github.com/modernman00/shared-js-lib#readme",
|
|
6
6
|
"keywords": [
|
|
@@ -23,24 +23,44 @@
|
|
|
23
23
|
"test": "jest"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"axios": "^1.
|
|
27
|
-
"axios-retry": "^4.5.0"
|
|
26
|
+
"axios": "^1.13.2",
|
|
27
|
+
"axios-retry": "^4.5.0",
|
|
28
|
+
"settings": "^0.1.1"
|
|
28
29
|
},
|
|
29
30
|
"publishConfig": {
|
|
30
31
|
"access": "public"
|
|
31
32
|
},
|
|
33
|
+
"files": [
|
|
34
|
+
"index.js",
|
|
35
|
+
"AcctMgt/",
|
|
36
|
+
"Cookie.js",
|
|
37
|
+
"CountryCode.js",
|
|
38
|
+
"DateTime.js",
|
|
39
|
+
"FileUpload.js",
|
|
40
|
+
"FormHelper.js",
|
|
41
|
+
"Http.js",
|
|
42
|
+
"Loader.js",
|
|
43
|
+
"ShowResponse.js",
|
|
44
|
+
"Utility.js",
|
|
45
|
+
"UtilityHtml.js",
|
|
46
|
+
"axiosWrapper.js",
|
|
47
|
+
"general.js",
|
|
48
|
+
"config/",
|
|
49
|
+
"README.md",
|
|
50
|
+
"release-history.md"
|
|
51
|
+
],
|
|
32
52
|
"devDependencies": {
|
|
33
53
|
"@babel/core": "^7.28.3",
|
|
34
54
|
"@babel/preset-env": "^7.28.3",
|
|
35
55
|
"@semantic-release/changelog": "^6.0.3",
|
|
36
56
|
"@semantic-release/git": "^10.0.1",
|
|
37
57
|
"@semantic-release/github": "^11.0.4",
|
|
38
|
-
"@semantic-release/npm": "^
|
|
58
|
+
"@semantic-release/npm": "^13.1.2",
|
|
39
59
|
"auto-changelog": "^2.5.0",
|
|
40
60
|
"babel-jest": "^30.0.5",
|
|
41
61
|
"jest": "^30.0.5",
|
|
42
62
|
"jest-environment-jsdom": "^30.0.5",
|
|
43
|
-
"semantic-release": "^
|
|
63
|
+
"semantic-release": "^25.0.2"
|
|
44
64
|
},
|
|
45
65
|
"release": {
|
|
46
66
|
"branches": [
|
package/release-history.md
CHANGED
|
@@ -19,3 +19,25 @@
|
|
|
19
19
|
### Changelog for v1.2.18
|
|
20
20
|
|
|
21
21
|
- 🔖 Bump version to v1.2.18 _(by modernman00)_
|
|
22
|
+
|
|
23
|
+
## v1.2.59 — 2025-12-21 21:50
|
|
24
|
+
### Changelog for v1.2.59
|
|
25
|
+
|
|
26
|
+
- 🔖 Bump version to v1.2.59 _(by modernman00)_
|
|
27
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
28
|
+
- 🔖 Bump version to v1.2.58 _(by modernman00)_
|
|
29
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
30
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
31
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
32
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
33
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
34
|
+
|
|
35
|
+
## v1.2.61 — 2026-01-27 09:25
|
|
36
|
+
### Changelog for v1.2.61
|
|
37
|
+
|
|
38
|
+
- 🔖 Bump version to v1.2.61 _(by modernman00)_
|
|
39
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
40
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
41
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
42
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
43
|
+
- 🧹 Auto-commit before release _(by modernman00)_
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
name: Release shared-js-lib
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
release:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- name: Checkout code
|
|
14
|
-
uses: actions/checkout@v3
|
|
15
|
-
|
|
16
|
-
- name: Setup Node.js
|
|
17
|
-
uses: actions/setup-node@v3
|
|
18
|
-
with:
|
|
19
|
-
node-version: '18'
|
|
20
|
-
registry-url: 'https://registry.npmjs.org/'
|
|
21
|
-
scope: '@modernman00'
|
|
22
|
-
|
|
23
|
-
- name: Install dependencies
|
|
24
|
-
run: npm ci
|
|
25
|
-
|
|
26
|
-
- name: Run tests
|
|
27
|
-
run: npm test
|
|
28
|
-
|
|
29
|
-
- name: Bump version and generate changelog
|
|
30
|
-
run: |
|
|
31
|
-
npm version patch -m "chore(release): %s"
|
|
32
|
-
npx auto-changelog -p
|
|
33
|
-
env:
|
|
34
|
-
GIT_AUTHOR_NAME: github-actions
|
|
35
|
-
GIT_AUTHOR_EMAIL: actions@github.com
|
|
36
|
-
GIT_COMMITTER_NAME: github-actions
|
|
37
|
-
GIT_COMMITTER_EMAIL: actions@github.com
|
|
38
|
-
|
|
39
|
-
- name: Push version bump and changelog
|
|
40
|
-
run: |
|
|
41
|
-
git push origin main --follow-tags
|
|
42
|
-
|
|
43
|
-
- name: Publish to npm
|
|
44
|
-
run: npm publish --access public
|
|
45
|
-
env:
|
|
46
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
47
|
-
|
|
48
|
-
- name: Create GitHub Release
|
|
49
|
-
uses: softprops/action-gh-release@v1
|
|
50
|
-
with:
|
|
51
|
-
tag_name: ${{ github.ref_name }}
|
|
52
|
-
env:
|
|
53
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*.*.*' # Trigger only on semantic version tags
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
release:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- name: Checkout code
|
|
14
|
-
uses: actions/checkout@v3
|
|
15
|
-
|
|
16
|
-
- name: Use Node.js
|
|
17
|
-
uses: actions/setup-node@v3
|
|
18
|
-
with:
|
|
19
|
-
node-version: '18'
|
|
20
|
-
registry-url: 'https://registry.npmjs.org/'
|
|
21
|
-
|
|
22
|
-
- name: Install dependencies
|
|
23
|
-
run: npm ci
|
|
24
|
-
|
|
25
|
-
- name: Publish to npm
|
|
26
|
-
run: npm publish --access public
|
|
27
|
-
env:
|
|
28
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
29
|
-
|
|
30
|
-
- name: Confirm publish
|
|
31
|
-
run: |
|
|
32
|
-
echo "🔍 Verifying publish..."
|
|
33
|
-
PACKAGE=$(node -p "require('./package.json').name")
|
|
34
|
-
VERSION=$(node -p "require('./package.json').version")
|
|
35
|
-
PUBLISHED=$(npm view $PACKAGE version)
|
|
36
|
-
if [[ "$PUBLISHED" == "$VERSION" ]]; then
|
|
37
|
-
echo "✅ $PACKAGE@$VERSION is live on npmjs.org"
|
|
38
|
-
else
|
|
39
|
-
echo "⚠️ Expected $VERSION, but registry shows $PUBLISHED"
|
|
40
|
-
fi
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
name: Run Tests and Coverage
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
test:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- name: Checkout repo
|
|
15
|
-
uses: actions/checkout@v3
|
|
16
|
-
|
|
17
|
-
- name: Setup Node.js
|
|
18
|
-
uses: actions/setup-node@v3
|
|
19
|
-
with:
|
|
20
|
-
node-version: '20'
|
|
21
|
-
|
|
22
|
-
- name: Install dependencies
|
|
23
|
-
run: npm ci
|
|
24
|
-
|
|
25
|
-
- name: Run tests with coverage
|
|
26
|
-
run: npm test -- --coverage
|
|
27
|
-
|
|
28
|
-
- name: Upload coverage report
|
|
29
|
-
uses: actions/upload-artifact@v3
|
|
30
|
-
with:
|
|
31
|
-
name: coverage-report
|
|
32
|
-
path: coverage/
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { createCodeSubmitHandler } from '../AcctMgt/code.js';
|
|
2
|
-
import { forgotSubmitHandler } from '../AcctMgt/forgot.js';
|
|
3
|
-
import { createAdminLoginHandler } from '../AcctMgt/login.js';
|
|
4
|
-
import { setupPasswordChange } from '../AcctMgt/changePassword.js';
|
|
5
|
-
import { loginSubmission } from '../AcctMgt/processAll.js';
|
|
6
|
-
import { showPassword, bindEvent } from '../Utility.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
jest.mock('../AcctMgt/processAll.js');
|
|
10
|
-
jest.mock('../Utility.js');
|
|
11
|
-
|
|
12
|
-
// Then patch the specific function after DOM is available
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
bindEvent.mockImplementation(({ id, event = 'click', handler }) => {
|
|
15
|
-
const el = document.getElementById(id);
|
|
16
|
-
if (el) el.addEventListener(event, handler);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
showPassword.mockImplementation(() => {});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// 🧪 Utility to scaffold DOM elements
|
|
23
|
-
const injectDOM = (elements) => {
|
|
24
|
-
elements.forEach(({ tag = 'button', id, value = '' }) => {
|
|
25
|
-
const el = document.createElement(tag);
|
|
26
|
-
el.setAttribute('id', id);
|
|
27
|
-
if (value) el.value = value;
|
|
28
|
-
document.body.appendChild(el);
|
|
29
|
-
});
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
document.body.innerHTML = '';
|
|
34
|
-
jest.clearAllMocks();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('AcctMgt Handlers', () => {
|
|
38
|
-
test('🧾 createCodeSubmitHandler submits code form correctly', () => {
|
|
39
|
-
injectDOM([{ id: 'button' }]);
|
|
40
|
-
|
|
41
|
-
createCodeSubmitHandler({
|
|
42
|
-
formId: 'codeForm',
|
|
43
|
-
inputId: 'code',
|
|
44
|
-
buttonId: 'button',
|
|
45
|
-
route: '/api/code',
|
|
46
|
-
redirect: '/success',
|
|
47
|
-
theme: 'bulma',
|
|
48
|
-
maxLength: 40
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
document.getElementById('button').dispatchEvent(new Event('click'));
|
|
52
|
-
|
|
53
|
-
expect(loginSubmission).toHaveBeenCalledWith(
|
|
54
|
-
'codeForm',
|
|
55
|
-
'/api/code',
|
|
56
|
-
'/success',
|
|
57
|
-
'bulma',
|
|
58
|
-
{ maxLength: { id: ['code'], max: [40] } }
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('📧 forgotSubmitHandler submits email form correctly', () => {
|
|
63
|
-
injectDOM([
|
|
64
|
-
{ id: 'button' },
|
|
65
|
-
{ tag: 'input', id: 'email', value: 'user@example.com' }
|
|
66
|
-
]);
|
|
67
|
-
|
|
68
|
-
forgotSubmitHandler({
|
|
69
|
-
formId: 'forgotPassword',
|
|
70
|
-
inputId: 'email',
|
|
71
|
-
buttonId: 'button',
|
|
72
|
-
route: '/api/forgot',
|
|
73
|
-
redirect: '/reset',
|
|
74
|
-
theme: 'bulma',
|
|
75
|
-
maxLength: 50
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
document.getElementById('button').dispatchEvent(new Event('click'));
|
|
79
|
-
|
|
80
|
-
expect(loginSubmission).toHaveBeenCalledWith(
|
|
81
|
-
'forgotPassword',
|
|
82
|
-
'/api/forgot',
|
|
83
|
-
'/reset',
|
|
84
|
-
'bulma',
|
|
85
|
-
{ maxLength: { id: ['email'], max: [50] } }
|
|
86
|
-
);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('🔐 createAdminLoginHandler sets attributes and submits login', () => {
|
|
90
|
-
injectDOM([
|
|
91
|
-
{ id: 'button' },
|
|
92
|
-
{ id: 'showPassword' },
|
|
93
|
-
{ tag: 'input', id: 'password' }
|
|
94
|
-
]);
|
|
95
|
-
|
|
96
|
-
createAdminLoginHandler({
|
|
97
|
-
formId: 'managed',
|
|
98
|
-
emailId: 'email',
|
|
99
|
-
passwordId: 'password',
|
|
100
|
-
buttonId: 'button',
|
|
101
|
-
showToggleId: 'showPassword',
|
|
102
|
-
route: '/api/login',
|
|
103
|
-
redirect: '/admin',
|
|
104
|
-
theme: 'bootstrap',
|
|
105
|
-
maxLength: [30, 50]
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
document.getElementById('button').dispatchEvent(new Event('click'));
|
|
109
|
-
document.getElementById('showPassword').dispatchEvent(new Event('click'));
|
|
110
|
-
|
|
111
|
-
expect(loginSubmission).toHaveBeenCalledWith(
|
|
112
|
-
'managed',
|
|
113
|
-
'/api/login',
|
|
114
|
-
'/admin',
|
|
115
|
-
'bootstrap',
|
|
116
|
-
{ maxLength: { id: ['password', 'email'], max: [30, 50] } }
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
expect(document.getElementById('password').getAttribute('autocomplete')).toBe('current-password');
|
|
120
|
-
expect(showPassword).toHaveBeenCalledWith('password');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('🔁 setupPasswordChange submits when passwords match', () => {
|
|
124
|
-
injectDOM([
|
|
125
|
-
{ id: 'button' },
|
|
126
|
-
{ id: 'showPassword' },
|
|
127
|
-
{ tag: 'input', id: 'password', value: 'Secure123!' },
|
|
128
|
-
{ tag: 'input', id: 'confirm_password', value: 'Secure123!' },
|
|
129
|
-
{ tag: 'div', id: 'confirm_password_error' },
|
|
130
|
-
{ tag: 'div', id: 'password_help' }
|
|
131
|
-
]);
|
|
132
|
-
|
|
133
|
-
setupPasswordChange({
|
|
134
|
-
buttonId: 'button',
|
|
135
|
-
passwordId: 'password',
|
|
136
|
-
confirmId: 'confirm_password',
|
|
137
|
-
errorId: 'confirm_password_error',
|
|
138
|
-
helpId: 'password_help',
|
|
139
|
-
showToggleId: 'showPassword',
|
|
140
|
-
route: '/api/change-password',
|
|
141
|
-
redirect: '/dashboard',
|
|
142
|
-
theme: 'bulma',
|
|
143
|
-
maxLength: 50
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
document.getElementById('button').dispatchEvent(new Event('click'));
|
|
147
|
-
|
|
148
|
-
expect(loginSubmission).toHaveBeenCalledWith(
|
|
149
|
-
'changePassword',
|
|
150
|
-
'/api/change-password',
|
|
151
|
-
'/dashboard',
|
|
152
|
-
'bulma',
|
|
153
|
-
{ maxLength: { id: ['password', 'confirm_password'], max: [50, 50] } }
|
|
154
|
-
);
|
|
155
|
-
});
|
|
156
|
-
});
|
package/babel.config.js
DELETED
package/jest.config.js
DELETED
package/release.sh
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -e
|
|
3
|
-
|
|
4
|
-
# --- Config ---
|
|
5
|
-
VERSION_FILE="config/version.php"
|
|
6
|
-
HISTORY_FILE="release-history.md"
|
|
7
|
-
REPO="origin"
|
|
8
|
-
DEFAULT_BRANCH="main"
|
|
9
|
-
|
|
10
|
-
# --- Utilities ---
|
|
11
|
-
function detect_branch() {
|
|
12
|
-
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
13
|
-
echo "🔍 Current branch: $CURRENT_BRANCH"
|
|
14
|
-
if [[ "$CURRENT_BRANCH" != "$DEFAULT_BRANCH" && "$CURRENT_BRANCH" != "master" ]]; then
|
|
15
|
-
echo "⚠️ You're on '$CURRENT_BRANCH'. Switch to '$DEFAULT_BRANCH' or 'master' before releasing."
|
|
16
|
-
exit 1
|
|
17
|
-
fi
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function ensure_clean_git() {
|
|
21
|
-
if [[ -n $(git status --porcelain) ]]; then
|
|
22
|
-
echo "🛑 Uncommitted changes detected."
|
|
23
|
-
git status
|
|
24
|
-
read -p "❓ Auto-commit all changes before release? (y/n): " choice
|
|
25
|
-
if [[ "$choice" == "y" ]]; then
|
|
26
|
-
git add .
|
|
27
|
-
git commit -m "🧹 Auto-commit before release"
|
|
28
|
-
else
|
|
29
|
-
echo "🚫 Aborting release."
|
|
30
|
-
exit 1
|
|
31
|
-
fi
|
|
32
|
-
fi
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function run_tests() {
|
|
36
|
-
echo "🧪 Running tests with coverage..."
|
|
37
|
-
npm test -- --coverage || {
|
|
38
|
-
echo "❌ Tests failed. Aborting release."
|
|
39
|
-
exit 1
|
|
40
|
-
}
|
|
41
|
-
echo "✅ Tests passed."
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function validate_dependencies() {
|
|
45
|
-
command -v gh >/dev/null || {
|
|
46
|
-
echo "❌ GitHub CLI (gh) not found. Install it to publish releases."
|
|
47
|
-
exit 1
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function extract_version() {
|
|
52
|
-
grep -q "APP_VERSION" "$VERSION_FILE" || {
|
|
53
|
-
echo "❌ APP_VERSION not found in $VERSION_FILE"
|
|
54
|
-
exit 1
|
|
55
|
-
}
|
|
56
|
-
CURRENT=$(grep "APP_VERSION" "$VERSION_FILE" | sed -E "s/.*'([^']+)'.*/\1/")
|
|
57
|
-
IFS='.' read -r MAJOR MINOR PATCH <<< "${CURRENT//v/}"
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function determine_bump_type() {
|
|
61
|
-
LAST_COMMIT=$(git log -1 --pretty=%s)
|
|
62
|
-
MODE="patch"
|
|
63
|
-
[[ "$LAST_COMMIT" == feat:* ]] && MODE="minor"
|
|
64
|
-
[[ "$LAST_COMMIT" == fix:* ]] && MODE="patch"
|
|
65
|
-
[[ "$LAST_COMMIT" == chore:* || "$LAST_COMMIT" == docs:* ]] && MODE="patch"
|
|
66
|
-
[[ "$LAST_COMMIT" == BREAKING:* ]] && MODE="major"
|
|
67
|
-
|
|
68
|
-
case "$MODE" in
|
|
69
|
-
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
|
|
70
|
-
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
|
|
71
|
-
patch) PATCH=$((PATCH + 1)) ;;
|
|
72
|
-
esac
|
|
73
|
-
|
|
74
|
-
SEMVER="${MAJOR}.${MINOR}.${PATCH}"
|
|
75
|
-
NEW="v$SEMVER"
|
|
76
|
-
echo "🔧 Current version: $CURRENT"
|
|
77
|
-
echo "⏫ Detected bump: $MODE → $NEW"
|
|
78
|
-
echo "📦 Last commit: $LAST_COMMIT"
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function update_version_file() {
|
|
82
|
-
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
83
|
-
sed -i '' "s/$CURRENT/$NEW/" "$VERSION_FILE"
|
|
84
|
-
else
|
|
85
|
-
sed -i "s/$CURRENT/$NEW/" "$VERSION_FILE"
|
|
86
|
-
fi
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function commit_and_tag() {
|
|
90
|
-
COMMIT_MSG="🔖 Bump version to $NEW"
|
|
91
|
-
npm version "$SEMVER" --no-git-tag-version
|
|
92
|
-
git add "$VERSION_FILE"
|
|
93
|
-
git commit -m "$COMMIT_MSG"
|
|
94
|
-
|
|
95
|
-
if git rev-parse "$NEW" >/dev/null 2>&1; then
|
|
96
|
-
echo "⚠️ Tag $NEW already exists. Skipping tag creation."
|
|
97
|
-
else
|
|
98
|
-
git tag "$NEW"
|
|
99
|
-
git push "$REPO" "$NEW"
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
echo "🚀 Pushing changes to $REPO/$DEFAULT_BRANCH..."
|
|
103
|
-
git push "$REPO" "$DEFAULT_BRANCH" || {
|
|
104
|
-
echo "❌ Failed to push to $DEFAULT_BRANCH. Check your Git credentials or branch name."
|
|
105
|
-
exit 1
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function generate_changelog() {
|
|
110
|
-
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
|
|
111
|
-
[ "$LAST_TAG" = "$NEW" ] && LAST_TAG=$(git rev-list --tags --max-count=2 | tail -n1 | xargs git describe --tags --abbrev=0)
|
|
112
|
-
NOTES="### Changelog for $NEW\n\n"
|
|
113
|
-
NOTES+=$(git log "$LAST_TAG"..HEAD --pretty=format:"- %s _(by %an)_" --no-merges)
|
|
114
|
-
[ -z "$NOTES" ] && NOTES="Initial release."
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function publish_github_release() {
|
|
118
|
-
gh release create "$NEW" \
|
|
119
|
-
--title "Release $NEW" \
|
|
120
|
-
--notes "$NOTES" \
|
|
121
|
-
--verify-tag
|
|
122
|
-
echo "✅ Published $NEW with changelog and updated $VERSION_FILE!"
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function log_release_history() {
|
|
126
|
-
echo -e "\n## $NEW — $(date '+%Y-%m-%d %H:%M')\n$NOTES" >> "$HISTORY_FILE"
|
|
127
|
-
echo "📝 Logged release to $HISTORY_FILE"
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function publish_to_npm() {
|
|
131
|
-
echo "🚀 Publishing $SEMVER to npm..."
|
|
132
|
-
npm publish --access public || {
|
|
133
|
-
echo "❌ npm publish failed. Check your credentials, registry access, or package.json config."
|
|
134
|
-
exit 1
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
echo "🔍 Verifying publish on npm registry..."
|
|
138
|
-
PACKAGE_NAME=$(node -p "require('./package.json').name")
|
|
139
|
-
PUBLISHED_VERSION=$(npm view "$PACKAGE_NAME" version)
|
|
140
|
-
|
|
141
|
-
if [[ "$PUBLISHED_VERSION" == "$SEMVER" ]]; then
|
|
142
|
-
echo "✅ npm publish confirmed: $PUBLISHED_VERSION is live on npmjs.org"
|
|
143
|
-
else
|
|
144
|
-
echo "⚠️ Publish may not have propagated yet. Expected $SEMVER, got $PUBLISHED_VERSION"
|
|
145
|
-
fi
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# --- Execution ---
|
|
150
|
-
detect_branch
|
|
151
|
-
ensure_clean_git
|
|
152
|
-
run_tests
|
|
153
|
-
validate_dependencies
|
|
154
|
-
extract_version
|
|
155
|
-
determine_bump_type
|
|
156
|
-
update_version_file
|
|
157
|
-
commit_and_tag
|
|
158
|
-
generate_changelog
|
|
159
|
-
publish_github_release
|
|
160
|
-
log_release_history
|
|
161
|
-
publish_to_npm
|
package/theAutoComplete.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { id } from './UtilityHtml.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
* @param {string} inputId
|
|
6
|
-
* @param {Array} arr
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Sets up an autocomplete functionality for an input element.
|
|
11
|
-
*
|
|
12
|
-
* @param {string} inputId - The ID of the input element to attach the autocomplete to.
|
|
13
|
-
* @param {Array<string>} arr - An array of strings containing the autocomplete suggestions.
|
|
14
|
-
*
|
|
15
|
-
* The function converts all strings in the array to lowercase for case-insensitive matching.
|
|
16
|
-
* It initializes the autocomplete with the given input element and array of suggestions,
|
|
17
|
-
* providing real-time suggestions based on user input. On selecting a suggestion,
|
|
18
|
-
* it populates the input element with the selected suggestion.
|
|
19
|
-
*/
|
|
20
|
-
export const autocomplete = (inputId, arr) => {
|
|
21
|
-
const whatToBuyInput = id(inputId); // Get the text input
|
|
22
|
-
if (whatToBuyInput) {
|
|
23
|
-
// Create a <ul> for autocomplete suggestions
|
|
24
|
-
const suggestionList = document.createElement("ul");
|
|
25
|
-
suggestionList.classList.add("autocomplete-suggestions");
|
|
26
|
-
suggestionList.id = "suggestions"; // For accessibility
|
|
27
|
-
whatToBuyInput.parentElement.appendChild(suggestionList); // Append to input's parent
|
|
28
|
-
|
|
29
|
-
// Function to show matching suggestions based on user input
|
|
30
|
-
const showSuggestions = (inputValue) => {
|
|
31
|
-
suggestionList.innerHTML = ""; // Clear previous suggestions
|
|
32
|
-
if (!inputValue) return; // Exit if input is empty
|
|
33
|
-
|
|
34
|
-
// Filter items that match the input (case-insensitive), limit to 8
|
|
35
|
-
const matches = arr
|
|
36
|
-
.filter(item => item.toLowerCase().includes(inputValue.toLowerCase()))
|
|
37
|
-
.slice(0, 8);
|
|
38
|
-
|
|
39
|
-
// Create <li> for each match
|
|
40
|
-
matches.forEach((item, index) => {
|
|
41
|
-
const li = document.createElement("li");
|
|
42
|
-
li.textContent = item;
|
|
43
|
-
li.setAttribute("tabindex", "0"); // Make focusable for keyboard
|
|
44
|
-
li.setAttribute("data-index", index); // Store index for navigation
|
|
45
|
-
li.addEventListener("click", () => {
|
|
46
|
-
whatToBuyInput.value = item; // Set input value on click
|
|
47
|
-
suggestionList.innerHTML = ""; // Clear suggestions
|
|
48
|
-
});
|
|
49
|
-
li.addEventListener("keypress", (e) => {
|
|
50
|
-
if (e.key === "Enter") {
|
|
51
|
-
whatToBuyInput.value = item; // Set input value on Enter
|
|
52
|
-
suggestionList.innerHTML = ""; // Clear suggestions
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
suggestionList.appendChild(li);
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// Show suggestions as user types
|
|
60
|
-
whatToBuyInput.addEventListener("input", (e) => {
|
|
61
|
-
showSuggestions(e.target.value);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Clear suggestions on blur (with delay for click to register)
|
|
65
|
-
whatToBuyInput.addEventListener("blur", () => {
|
|
66
|
-
setTimeout(() => suggestionList.innerHTML = "", 200);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Handle keyboard navigation for suggestions
|
|
70
|
-
whatToBuyInput.addEventListener("keydown", (e) => {
|
|
71
|
-
const suggestions = suggestionList.querySelectorAll("li");
|
|
72
|
-
if (!suggestions.length) return; // Exit if no suggestions
|
|
73
|
-
|
|
74
|
-
let focusedIndex = Array.from(suggestions).findIndex(li => li === document.activeElement);
|
|
75
|
-
if (e.key === "ArrowDown") {
|
|
76
|
-
e.preventDefault(); // Prevent cursor movement
|
|
77
|
-
focusedIndex = (focusedIndex + 1) % suggestions.length; // Loop to start
|
|
78
|
-
suggestions[focusedIndex].focus();
|
|
79
|
-
} else if (e.key === "ArrowUp") {
|
|
80
|
-
e.preventDefault();
|
|
81
|
-
focusedIndex = (focusedIndex - 1 + suggestions.length) % suggestions.length; // Loop to end
|
|
82
|
-
suggestions[focusedIndex].focus();
|
|
83
|
-
} else if (e.key === "Enter" && focusedIndex >= 0) {
|
|
84
|
-
e.preventDefault();
|
|
85
|
-
whatToBuyInput.value = suggestions[focusedIndex].textContent; // Select item
|
|
86
|
-
suggestionList.innerHTML = ""; // Clear suggestions
|
|
87
|
-
} else if (e.key === "Escape") {
|
|
88
|
-
suggestionList.innerHTML = ""; // Clear suggestions
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|