webext-schema 7.0.0 → 7.1.1
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/.github/workflows/node.js.yml +2 -2
- package/.prettierignore +8 -0
- package/README.md +37 -6
- package/SECURITY.md +30 -0
- package/eslint.config.mjs +27 -19
- package/index.js +9 -3
- package/modules/browser.js +81 -25
- package/modules/commander.js +8 -5
- package/modules/common.js +1 -1
- package/modules/file-util.js +23 -10
- package/modules/schema.js +21 -14
- package/modules/update.js +34 -23
- package/package.json +25 -24
- package/prettier.config.mjs +14 -0
- package/schemas/beta/webext.json +902 -204
- package/schemas/central/webext.json +902 -204
- package/schemas/mail/mailext.json +552 -17
- package/test/browser.test.js +1045 -495
- package/test/commander.test.js +7 -21
- package/test/common.test.js +7 -1
- package/test/file-util.test.js +65 -35
- package/test/index.test.js +55 -1
- package/test/schema.test.js +839 -430
- package/test/update.test.js +872 -511
- package/types/index.d.ts +8 -1
- package/types/modules/browser.d.ts +61 -61
- package/types/modules/commander.d.ts +3 -3
- package/types/modules/common.d.ts +7 -7
- package/types/modules/constant.d.ts +3 -3
- package/types/modules/file-util.d.ts +16 -16
- package/types/modules/schema.d.ts +6 -6
- package/types/modules/update.d.ts +10 -10
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
build:
|
|
18
18
|
strategy:
|
|
19
19
|
matrix:
|
|
20
|
-
node-version: [
|
|
20
|
+
node-version: [ 22.x, lts/*, latest ]
|
|
21
21
|
runs-on: ubuntu-latest
|
|
22
22
|
steps:
|
|
23
23
|
- uses: actions/checkout@v6
|
|
@@ -28,4 +28,4 @@ jobs:
|
|
|
28
28
|
check-latest: true
|
|
29
29
|
- run: npm ci
|
|
30
30
|
- run: npm run lint
|
|
31
|
-
- run: npm run
|
|
31
|
+
- run: npm run test:all
|
package/.prettierignore
ADDED
package/README.md
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
# webext-schema
|
|
2
|
+
|
|
1
3
|
[](https://github.com/asamuzaK/webext-schema/actions?query=workflow%3Abuild)
|
|
2
4
|
[](https://github.com/asamuzaK/webext-schema/actions/workflows/github-code-scanning/codeql)
|
|
3
5
|
[](https://www.npmjs.com/package/webext-schema)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
[WebExtensions](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions "Browser Extensions - Mozilla | MDN") schemas and [MailExtensions](https://developer.thunderbird.net/add-ons/mailextensions "A Guide to Extensions - Thunderbird") schemas fetched from [hg.mozilla.org](https://hg.mozilla.org/).
|
|
8
|
+
Stubbed browser API and a standalone browser utility script are also available.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
* **Up-to-date Schemas:** Directly fetched from Mozilla's repositories.
|
|
13
|
+
* **Multi-Channel Support:** Choose between `beta`, `central`, and `mail` channels to match your target environment.
|
|
14
|
+
* **Easy API Mocking:** Instantly generate a stubbed `browser` object powered by [Sinon.JS](https://sinonjs.org/) for robust unit testing of your extensions.
|
|
15
|
+
* **Standalone Utility Included:** Comes with `browser.js`, a dependency-free, Promise-wrapped utility script ready to be dropped into your extension project for easier API handling.
|
|
16
|
+
* **Modern ESM & TypeScript:** Native ES Modules support with bundled TypeScript definitions (`index.d.ts`).
|
|
9
17
|
|
|
10
18
|
## Install
|
|
11
19
|
|
|
@@ -13,7 +21,7 @@ Stubbed browser API is also available.
|
|
|
13
21
|
npm i webext-schema
|
|
14
22
|
```
|
|
15
23
|
|
|
16
|
-
## Usage
|
|
24
|
+
## Usage (Schema & Mock)
|
|
17
25
|
|
|
18
26
|
```javascript
|
|
19
27
|
import { Schema } from 'webext-schema';
|
|
@@ -23,7 +31,7 @@ const schema = new Schema();
|
|
|
23
31
|
|
|
24
32
|
### new Schema(<var>channel</var>, <var>sinonConfig</var>)
|
|
25
33
|
|
|
26
|
-
* @param {string} [channel] - release channel. one of "beta", "central", "
|
|
34
|
+
* @param {string} [channel] - release channel. one of "beta", "central", "mail".
|
|
27
35
|
* @param {Object} [sinonConfig] - optional configuration for `sinon.createSandbox()`, see [Sinon.JS](https://sinonjs.org/) for details.
|
|
28
36
|
|
|
29
37
|
Both arguments are optional.
|
|
@@ -37,7 +45,7 @@ const schema = new Schema("central", {
|
|
|
37
45
|
|
|
38
46
|
### schema.channel
|
|
39
47
|
|
|
40
|
-
"central", "beta"
|
|
48
|
+
"central", "beta" for WebExtensions, and "mail" for MailExtensions are available.
|
|
41
49
|
Channel defaults to "beta".
|
|
42
50
|
|
|
43
51
|
```javascript
|
|
@@ -125,3 +133,26 @@ assert.isFunction(port2.onDisconnect.addListener);
|
|
|
125
133
|
// reset
|
|
126
134
|
browser._sandbox.reset();
|
|
127
135
|
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Browser Utility (`browser.js`)
|
|
140
|
+
|
|
141
|
+
A standalone utility script (`browser.js`) is also included in this repository. It provides useful, Promise-wrapped functions for common browser extension APIs (e.g., `browser.tabs`, `browser.runtime`, `browser.storage`).
|
|
142
|
+
|
|
143
|
+
This file is designed to be copied directly into your browser extension project. It has no external dependencies and works natively in your background or content scripts.
|
|
144
|
+
|
|
145
|
+
### How to use `browser.js`
|
|
146
|
+
|
|
147
|
+
1. Copy `browser.js` from the `webext-schema` repository into your extension's source directory.
|
|
148
|
+
2. Import the needed functions into your extension scripts:
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
import { createTab, getActiveTab, setStorage } from './path/to/browser.js';
|
|
152
|
+
|
|
153
|
+
// Example: Create a new tab and save data
|
|
154
|
+
async function openAndSave() {
|
|
155
|
+
const tab = await createTab({ url: '[https://example.com](https://example.com)' });
|
|
156
|
+
await setStorage({ lastOpenedTabId: tab.id }, 'local');
|
|
157
|
+
}
|
|
158
|
+
```
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
We take security vulnerabilities seriously. Thank you for improving the security of our project.
|
|
4
|
+
|
|
5
|
+
## Supported Versions
|
|
6
|
+
|
|
7
|
+
We currently provide security updates **only for the latest major release**.
|
|
8
|
+
|
|
9
|
+
## Reporting a Vulnerability
|
|
10
|
+
|
|
11
|
+
Please **DO NOT** open a public issue for security-related vulnerabilities.
|
|
12
|
+
|
|
13
|
+
If you discover a security vulnerability, please report it privately via **GitHub's Private vulnerability reporting** feature.
|
|
14
|
+
You can do this by navigating to the **Security** tab of this repository, selecting **Advisories**, and clicking the **Report a vulnerability** button.
|
|
15
|
+
|
|
16
|
+
To help us resolve the issue efficiently, please include the following details in your report:
|
|
17
|
+
|
|
18
|
+
* **Type of vulnerability** (e.g., XSS, SSRF, Prototype Pollution)
|
|
19
|
+
* **Affected component or file name**
|
|
20
|
+
* **Steps to reproduce** (including proof of concept / PoC code if available)
|
|
21
|
+
* **Potential impact** of the vulnerability
|
|
22
|
+
|
|
23
|
+
### Our Response Process
|
|
24
|
+
|
|
25
|
+
1. **Acknowledgment**: We will acknowledge receipt of your report within 48 to 72 hours.
|
|
26
|
+
2. **Investigation**: We will verify the vulnerability and evaluate its severity.
|
|
27
|
+
3. **Fix & Release**: We will work on a fix in a private fork and release a patched version as soon as possible.
|
|
28
|
+
4. **Advisory**: A GitHub Security Advisory will be published if necessary, giving you full credit for the discovery (unless you prefer to remain anonymous).
|
|
29
|
+
|
|
30
|
+
Please adhere to Coordinated Vulnerability Disclosure (CVD) and **do not disclose the vulnerability publicly** until a patch has been released.
|
package/eslint.config.mjs
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import jsdoc from 'eslint-plugin-jsdoc';
|
|
2
|
+
import prettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
2
3
|
import regexp from 'eslint-plugin-regexp';
|
|
3
4
|
import unicorn from 'eslint-plugin-unicorn';
|
|
4
5
|
import globals from 'globals';
|
|
5
|
-
import neostandard
|
|
6
|
+
import neostandard from 'neostandard';
|
|
6
7
|
|
|
7
8
|
export default [
|
|
8
9
|
...neostandard({
|
|
9
|
-
|
|
10
|
+
noStyle: true
|
|
10
11
|
}),
|
|
11
12
|
jsdoc.configs['flat/recommended'],
|
|
12
13
|
regexp.configs['flat/recommended'],
|
|
14
|
+
prettierRecommended,
|
|
15
|
+
{
|
|
16
|
+
ignores: ['test/file/']
|
|
17
|
+
},
|
|
13
18
|
{
|
|
14
19
|
languageOptions: {
|
|
15
20
|
globals: {
|
|
@@ -22,29 +27,32 @@ export default [
|
|
|
22
27
|
reportUnusedDisableDirectives: true
|
|
23
28
|
},
|
|
24
29
|
plugins: {
|
|
25
|
-
'@stylistic': neostdplugins['@stylistic'],
|
|
26
30
|
regexp,
|
|
27
31
|
unicorn
|
|
28
32
|
},
|
|
33
|
+
settings: {
|
|
34
|
+
jsdoc: {
|
|
35
|
+
mode: 'typescript'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
29
38
|
rules: {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'import-x/order': ['error', {
|
|
36
|
-
alphabetize: {
|
|
37
|
-
order: 'ignore',
|
|
38
|
-
caseInsensitive: false
|
|
39
|
+
curly: ['error', 'all'],
|
|
40
|
+
'jsdoc/no-undefined-types': [
|
|
41
|
+
'error',
|
|
42
|
+
{
|
|
43
|
+
definedTypes: ['ReadonlyArray']
|
|
39
44
|
}
|
|
40
|
-
|
|
45
|
+
],
|
|
41
46
|
'no-await-in-loop': 'error',
|
|
42
|
-
'no-use-before-define': [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
'no-use-before-define': [
|
|
48
|
+
'error',
|
|
49
|
+
{
|
|
50
|
+
allowNamedExports: false,
|
|
51
|
+
classes: true,
|
|
52
|
+
functions: true,
|
|
53
|
+
variables: true
|
|
54
|
+
}
|
|
55
|
+
],
|
|
48
56
|
'prefer-object-has-own': 'error',
|
|
49
57
|
'unicorn/prefer-node-protocol': 'error'
|
|
50
58
|
}
|
package/index.js
CHANGED
|
@@ -9,11 +9,17 @@
|
|
|
9
9
|
/* api */
|
|
10
10
|
import process from 'node:process';
|
|
11
11
|
import { parseCommand } from './modules/commander.js';
|
|
12
|
-
import { logErr, throwErr } from './modules/common.js';
|
|
13
12
|
export { Schema } from './modules/schema.js';
|
|
14
13
|
|
|
15
14
|
/* process */
|
|
16
|
-
process.on('uncaughtException',
|
|
17
|
-
|
|
15
|
+
process.on('uncaughtException', err => {
|
|
16
|
+
console.error('Fatal Error (Uncaught Exception):', err);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
process.on('unhandledRejection', reason => {
|
|
21
|
+
console.error('Fatal Error (Unhandled Rejection):', reason);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
18
24
|
|
|
19
25
|
parseCommand(process.argv);
|
package/modules/browser.js
CHANGED
|
@@ -2,12 +2,48 @@
|
|
|
2
2
|
* browser.js
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
/* shared */
|
|
6
|
-
import { getType, isObjectNotEmpty, isString, logErr } from './common.js';
|
|
7
|
-
|
|
8
5
|
/* api */
|
|
9
6
|
const { permissions, runtime, tabs, windows } = browser;
|
|
10
7
|
|
|
8
|
+
/* constants & utils */
|
|
9
|
+
const TYPE_FROM = 8;
|
|
10
|
+
const TYPE_TO = -1;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* log error
|
|
14
|
+
* @param {!object} e - Error
|
|
15
|
+
* @returns {boolean} - false
|
|
16
|
+
*/
|
|
17
|
+
const logErr = e => {
|
|
18
|
+
console.error(e);
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* get type
|
|
24
|
+
* @param {object} o - object to check
|
|
25
|
+
* @returns {string} - type of object
|
|
26
|
+
*/
|
|
27
|
+
const getType = o =>
|
|
28
|
+
Object.prototype.toString.call(o).slice(TYPE_FROM, TYPE_TO);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* is object, and not an empty object
|
|
32
|
+
* @param {object} o - object to check;
|
|
33
|
+
* @returns {boolean} - result
|
|
34
|
+
*/
|
|
35
|
+
const isObjectNotEmpty = o => {
|
|
36
|
+
const items = /Object/i.test(getType(o)) && Object.keys(o);
|
|
37
|
+
return !!items?.length;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* is string
|
|
42
|
+
* @param {object} o - object to check
|
|
43
|
+
* @returns {boolean} - result
|
|
44
|
+
*/
|
|
45
|
+
const isString = o => typeof o === 'string' || o instanceof String;
|
|
46
|
+
|
|
11
47
|
/**
|
|
12
48
|
* check if permission is granted
|
|
13
49
|
* @param {object} perm - permissions.Permission
|
|
@@ -70,7 +106,9 @@ export const getCloseTabsByDoubleClickValue = async () => {
|
|
|
70
106
|
});
|
|
71
107
|
let userValue;
|
|
72
108
|
if (isGranted) {
|
|
73
|
-
const {
|
|
109
|
+
const {
|
|
110
|
+
browserSettings: { closeTabsByDoubleClick }
|
|
111
|
+
} = browser;
|
|
74
112
|
userValue = await closeTabsByDoubleClick.get({});
|
|
75
113
|
}
|
|
76
114
|
return userValue ?? null;
|
|
@@ -86,7 +124,9 @@ export const setContextMenuOnMouseup = async () => {
|
|
|
86
124
|
});
|
|
87
125
|
let res;
|
|
88
126
|
if (isGranted) {
|
|
89
|
-
const {
|
|
127
|
+
const {
|
|
128
|
+
browserSettings: { contextMenuShowEvent }
|
|
129
|
+
} = browser;
|
|
90
130
|
const { levelOfControl, value } = await contextMenuShowEvent.get({});
|
|
91
131
|
if (value === 'mouseup') {
|
|
92
132
|
res = true;
|
|
@@ -109,7 +149,9 @@ export const clearContextMenuOnMouseup = async () => {
|
|
|
109
149
|
});
|
|
110
150
|
let res;
|
|
111
151
|
if (isGranted) {
|
|
112
|
-
const {
|
|
152
|
+
const {
|
|
153
|
+
browserSettings: { contextMenuShowEvent }
|
|
154
|
+
} = browser;
|
|
113
155
|
res = await contextMenuShowEvent.clear({});
|
|
114
156
|
}
|
|
115
157
|
return !!res;
|
|
@@ -125,7 +167,9 @@ export const getNewTabPositionValue = async () => {
|
|
|
125
167
|
});
|
|
126
168
|
let res;
|
|
127
169
|
if (isGranted) {
|
|
128
|
-
const {
|
|
170
|
+
const {
|
|
171
|
+
browserSettings: { newTabPosition }
|
|
172
|
+
} = browser;
|
|
129
173
|
res = await newTabPosition.get({});
|
|
130
174
|
}
|
|
131
175
|
return res ?? null;
|
|
@@ -143,8 +187,9 @@ export const isCommandCustomizable = async () => {
|
|
|
143
187
|
let bool;
|
|
144
188
|
if (isGranted) {
|
|
145
189
|
const { commands } = browser;
|
|
146
|
-
bool =
|
|
147
|
-
|
|
190
|
+
bool =
|
|
191
|
+
typeof commands.update === 'function' &&
|
|
192
|
+
typeof commands.reset === 'function';
|
|
148
193
|
}
|
|
149
194
|
return !!bool;
|
|
150
195
|
};
|
|
@@ -165,11 +210,16 @@ export const updateCommand = async (id, value = '') => {
|
|
|
165
210
|
let func;
|
|
166
211
|
if (await isCommandCustomizable()) {
|
|
167
212
|
const { commands } = browser;
|
|
168
|
-
const shortcut =
|
|
169
|
-
|
|
213
|
+
const shortcut = value
|
|
214
|
+
.trim()
|
|
215
|
+
.replace(/\+([a-z])$/, (m, c) => `+${c.toUpperCase()}`);
|
|
170
216
|
if (shortcut === '') {
|
|
171
217
|
func = commands.reset(id);
|
|
172
|
-
} else if (
|
|
218
|
+
} else if (
|
|
219
|
+
/^(?:(?:Alt|Command|(?:Mac)?Ctrl)(?:\+Shift)?|Alt\+(?:Command|(?:Mac)?Ctrl)|Command\+(?:Alt|MacCtrl)|Ctrl\+(?:Alt|MacCtrl)|MacCtrl\+(?:Alt|Command|Ctrl))\+[\dA-Z]|F[1-9]|Media(?:(?:Next|Prev)Track|PlayPause|Stop)$/.test(
|
|
220
|
+
shortcut
|
|
221
|
+
)
|
|
222
|
+
) {
|
|
173
223
|
func = commands.update({
|
|
174
224
|
shortcut,
|
|
175
225
|
name: id
|
|
@@ -1027,19 +1077,24 @@ export const warmupTab = async tabId => {
|
|
|
1027
1077
|
export const captureVisibleTab = async (windowId, opt) => {
|
|
1028
1078
|
const info = await runtime.getBrowserInfo();
|
|
1029
1079
|
let isGranted;
|
|
1030
|
-
if (info.
|
|
1080
|
+
if (info.vendor === 'Mozilla') {
|
|
1031
1081
|
const browserVersion = parseFloat(info.version);
|
|
1032
|
-
isGranted =
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1082
|
+
isGranted =
|
|
1083
|
+
(browserVersion >= 126 &&
|
|
1084
|
+
(await isPermissionGranted({
|
|
1085
|
+
permissions: ['activeTab']
|
|
1086
|
+
}))) ||
|
|
1087
|
+
(await isPermissionGranted({
|
|
1088
|
+
origins: ['<all_urls>']
|
|
1089
|
+
}));
|
|
1037
1090
|
} else {
|
|
1038
|
-
isGranted =
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1091
|
+
isGranted =
|
|
1092
|
+
(await isPermissionGranted({
|
|
1093
|
+
permissions: ['activeTab']
|
|
1094
|
+
})) ||
|
|
1095
|
+
(await isPermissionGranted({
|
|
1096
|
+
origins: ['<all_urls>']
|
|
1097
|
+
}));
|
|
1043
1098
|
}
|
|
1044
1099
|
let url;
|
|
1045
1100
|
if (isGranted) {
|
|
@@ -1089,8 +1144,9 @@ export const getCurrentTheme = async windowId => {
|
|
|
1089
1144
|
let currentTheme;
|
|
1090
1145
|
if (isGranted) {
|
|
1091
1146
|
const { theme } = browser;
|
|
1092
|
-
currentTheme =
|
|
1093
|
-
|
|
1147
|
+
currentTheme = await theme.getCurrent(
|
|
1148
|
+
Number.isInteger(windowId) ? windowId : null
|
|
1149
|
+
);
|
|
1094
1150
|
}
|
|
1095
1151
|
return currentTheme ?? null;
|
|
1096
1152
|
};
|
package/modules/commander.js
CHANGED
|
@@ -38,13 +38,18 @@ export const parseCommand = args => {
|
|
|
38
38
|
commander.exitOverride();
|
|
39
39
|
commander.version(process.env.npm_package_version, '-v, --version');
|
|
40
40
|
if (args.includes('clean') || args.includes('c')) {
|
|
41
|
-
commander
|
|
41
|
+
commander
|
|
42
|
+
.command('clean')
|
|
43
|
+
.alias('c')
|
|
42
44
|
.description('clean directory')
|
|
43
45
|
.option('-d, --dir <name>', 'specify directory')
|
|
44
46
|
.option('-i, --info', 'console info')
|
|
45
47
|
.action(cleanDirectory);
|
|
46
48
|
} else if (args.includes('update') || args.includes('u')) {
|
|
47
|
-
commander
|
|
49
|
+
commander
|
|
50
|
+
.command('update')
|
|
51
|
+
.alias('u')
|
|
52
|
+
.description('update schemas')
|
|
48
53
|
.option('-c, --channel <name>', 'specify the release channel')
|
|
49
54
|
.option('-i, --info', 'console info')
|
|
50
55
|
.action(updateSchemas);
|
|
@@ -54,6 +59,4 @@ export const parseCommand = args => {
|
|
|
54
59
|
};
|
|
55
60
|
|
|
56
61
|
/* For test */
|
|
57
|
-
export {
|
|
58
|
-
commander
|
|
59
|
-
};
|
|
62
|
+
export { commander };
|
package/modules/common.js
CHANGED
package/modules/file-util.js
CHANGED
|
@@ -94,8 +94,9 @@ export const isExecutable = (file, mask = MASK_BIT) => {
|
|
|
94
94
|
let res;
|
|
95
95
|
const stat = getStat(file);
|
|
96
96
|
if (stat) {
|
|
97
|
-
res =
|
|
98
|
-
|
|
97
|
+
res =
|
|
98
|
+
!!(stat.mode & mask) ||
|
|
99
|
+
(IS_WIN && /\.(?:bat|cmd|exe|ps1|wsh)$/i.test(file));
|
|
99
100
|
}
|
|
100
101
|
return !!res;
|
|
101
102
|
};
|
|
@@ -118,8 +119,11 @@ export const getFileTimestamp = file => {
|
|
|
118
119
|
*/
|
|
119
120
|
export const getFileNameFromFilePath = (file, subst = SUBST) => {
|
|
120
121
|
let name;
|
|
121
|
-
if (
|
|
122
|
-
|
|
122
|
+
if (
|
|
123
|
+
isString(file) &&
|
|
124
|
+
isFile(file) &&
|
|
125
|
+
/^[^.]+(?:\..+)?$/.test(path.basename(file))
|
|
126
|
+
) {
|
|
123
127
|
[, name] = /^([^.]+)(?:\..+)?$/.exec(path.basename(file));
|
|
124
128
|
}
|
|
125
129
|
return name || subst;
|
|
@@ -179,7 +183,7 @@ export const createDirectory = async (dir, mode = PERM_DIR) => {
|
|
|
179
183
|
mode,
|
|
180
184
|
recursive: true
|
|
181
185
|
};
|
|
182
|
-
!isDir(dirPath) && await fsPromise.mkdir(dirPath, opt);
|
|
186
|
+
!isDir(dirPath) && (await fsPromise.mkdir(dirPath, opt));
|
|
183
187
|
return dirPath;
|
|
184
188
|
};
|
|
185
189
|
|
|
@@ -193,14 +197,23 @@ export const createDirectory = async (dir, mode = PERM_DIR) => {
|
|
|
193
197
|
* @param {number|string} [opt.mode] - file permission
|
|
194
198
|
* @returns {Promise.<string>} - file path
|
|
195
199
|
*/
|
|
196
|
-
export const createFile = async (
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
export const createFile = async (
|
|
201
|
+
file,
|
|
202
|
+
value,
|
|
203
|
+
opt = {
|
|
204
|
+
encoding: null,
|
|
205
|
+
flag: 'w',
|
|
206
|
+
mode: PERM_FILE
|
|
207
|
+
}
|
|
208
|
+
) => {
|
|
199
209
|
if (!isString(file)) {
|
|
200
210
|
throw new TypeError(`Expected String but got ${getType(file)}.`);
|
|
201
211
|
}
|
|
202
|
-
if (
|
|
203
|
-
|
|
212
|
+
if (
|
|
213
|
+
!isString(value) &&
|
|
214
|
+
!Buffer.isBuffer(value) &&
|
|
215
|
+
!(value instanceof Uint8Array)
|
|
216
|
+
) {
|
|
204
217
|
throw new TypeError(
|
|
205
218
|
`Expected String, Buffer, Uint8Array but got ${getType(value)}.`
|
|
206
219
|
);
|
package/modules/schema.js
CHANGED
|
@@ -28,9 +28,7 @@ export class Schema {
|
|
|
28
28
|
constructor(...args) {
|
|
29
29
|
const [arg1, arg2] = args;
|
|
30
30
|
this.#channel =
|
|
31
|
-
isString(arg1) && /^(?:central|mail)$/.test(arg1)
|
|
32
|
-
? arg1
|
|
33
|
-
: 'beta';
|
|
31
|
+
isString(arg1) && /^(?:central|mail)$/.test(arg1) ? arg1 : 'beta';
|
|
34
32
|
this._sandbox =
|
|
35
33
|
(isObjectNotEmpty(arg1) && sinon.createSandbox(arg1)) ||
|
|
36
34
|
(isObjectNotEmpty(arg2) && sinon.createSandbox(arg2)) ||
|
|
@@ -209,12 +207,18 @@ export class Schema {
|
|
|
209
207
|
target[key] = item.value;
|
|
210
208
|
} else if (type === 'function') {
|
|
211
209
|
target[key] = this._sandbox.stub();
|
|
212
|
-
} else if (
|
|
213
|
-
|
|
210
|
+
} else if (
|
|
211
|
+
type === 'object' ||
|
|
212
|
+
$ref ||
|
|
213
|
+
Object.hasOwn(item, 'properties')
|
|
214
|
+
) {
|
|
214
215
|
target[key] ??= {};
|
|
215
|
-
properties &&
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
properties &&
|
|
217
|
+
this._mockProperties(
|
|
218
|
+
target[key],
|
|
219
|
+
properties,
|
|
220
|
+
`${namespace}.${key}`
|
|
221
|
+
);
|
|
218
222
|
} else {
|
|
219
223
|
target[key] = null;
|
|
220
224
|
}
|
|
@@ -339,14 +343,17 @@ export class Schema {
|
|
|
339
343
|
for (const [key, value] of schemaItems) {
|
|
340
344
|
const items = Object.values(value);
|
|
341
345
|
for (const item of items) {
|
|
342
|
-
const {
|
|
343
|
-
|
|
344
|
-
} = item;
|
|
346
|
+
const { $import, events, functions, namespace, properties, types } =
|
|
347
|
+
item;
|
|
345
348
|
const fileKey = camelize(key.replace(/\.json$/, '')).toLowerCase();
|
|
346
349
|
const itemKey = namespace.replace(/\./g, '').toLowerCase();
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
+
if (
|
|
351
|
+
fileKey === itemKey ||
|
|
352
|
+
fileKey.startsWith(itemKey) ||
|
|
353
|
+
itemKey.startsWith(fileKey) ||
|
|
354
|
+
aliasKeys.includes(itemKey) ||
|
|
355
|
+
$import
|
|
356
|
+
) {
|
|
350
357
|
const mapKey = [];
|
|
351
358
|
let ns;
|
|
352
359
|
if (namespace.includes('.')) {
|
package/modules/update.js
CHANGED
|
@@ -50,7 +50,7 @@ export const getChannelUrl = channel => {
|
|
|
50
50
|
default:
|
|
51
51
|
dir = 'releases/mozilla-beta/';
|
|
52
52
|
}
|
|
53
|
-
return `https://hg.mozilla.org/${dir}raw-file/tip/`;
|
|
53
|
+
return `https://hg-edge.mozilla.org/${dir}raw-file/tip/`;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -70,7 +70,8 @@ export const getSchemaData = async (file, baseUrl) => {
|
|
|
70
70
|
const text = await fetchText(href);
|
|
71
71
|
const schema = JSON5.parse(text);
|
|
72
72
|
return {
|
|
73
|
-
file,
|
|
73
|
+
file,
|
|
74
|
+
schema
|
|
74
75
|
};
|
|
75
76
|
};
|
|
76
77
|
|
|
@@ -158,10 +159,7 @@ export const getMailExtSchemaData = async (baseUrl, info) => {
|
|
|
158
159
|
}
|
|
159
160
|
const items = await getFileList(baseUrl);
|
|
160
161
|
const schemaUrl = `${baseUrl}schemas/`;
|
|
161
|
-
const excludeFile = [
|
|
162
|
-
'commands.json',
|
|
163
|
-
'pkcs11.json'
|
|
164
|
-
];
|
|
162
|
+
const excludeFile = ['commands.json', 'pkcs11.json'];
|
|
165
163
|
const schemas = [];
|
|
166
164
|
for (const item of items) {
|
|
167
165
|
if (!excludeFile.includes(item)) {
|
|
@@ -185,26 +183,33 @@ export const createUnifiedSchema = async (channel, info) => {
|
|
|
185
183
|
const channelUrl = getChannelUrl(channel);
|
|
186
184
|
const schema = {};
|
|
187
185
|
if (channel === 'mail') {
|
|
188
|
-
const browserItems = [
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
await getListedSchemaData(browserUrl, browserItems, info);
|
|
186
|
+
const browserItems = ['commands.json', 'pkcs11.json'];
|
|
187
|
+
const browserUrl = `${getChannelUrl('central')}browser/components/extensions/schemas/`;
|
|
188
|
+
const browserSchemas = await getListedSchemaData(
|
|
189
|
+
browserUrl,
|
|
190
|
+
browserItems,
|
|
191
|
+
info
|
|
192
|
+
);
|
|
196
193
|
for (const item of browserSchemas) {
|
|
197
194
|
const { file, schema: itemSchema } = item;
|
|
198
195
|
schema[file] = itemSchema;
|
|
199
196
|
}
|
|
200
197
|
const toolkitItems = [
|
|
201
|
-
'content_scripts.json',
|
|
202
|
-
'
|
|
198
|
+
'content_scripts.json',
|
|
199
|
+
'experiments.json',
|
|
200
|
+
'extension.json',
|
|
201
|
+
'i18n.json',
|
|
202
|
+
'management.json',
|
|
203
|
+
'permissions.json',
|
|
204
|
+
'runtime.json',
|
|
205
|
+
'theme.json'
|
|
203
206
|
];
|
|
204
|
-
const toolkitUrl =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
const toolkitUrl = `${getChannelUrl('central')}toolkit/components/extensions/schemas/`;
|
|
208
|
+
const toolkitSchemas = await getListedSchemaData(
|
|
209
|
+
toolkitUrl,
|
|
210
|
+
toolkitItems,
|
|
211
|
+
info
|
|
212
|
+
);
|
|
208
213
|
for (const item of toolkitSchemas) {
|
|
209
214
|
const { file, schema: itemSchema } = item;
|
|
210
215
|
schema[file] = itemSchema;
|
|
@@ -245,10 +250,16 @@ export const saveSchemaFile = async (channel, info) => {
|
|
|
245
250
|
const schema = await createUnifiedSchema(channel, info);
|
|
246
251
|
const content = `${JSON.stringify(schema, null, INDENT)}\n`;
|
|
247
252
|
const fileName = channel === 'mail' ? 'mailext' : 'webext';
|
|
248
|
-
const filePath =
|
|
249
|
-
|
|
253
|
+
const filePath = path.resolve(
|
|
254
|
+
DIR_CWD,
|
|
255
|
+
'schemas',
|
|
256
|
+
channel,
|
|
257
|
+
`${fileName}.json`
|
|
258
|
+
);
|
|
250
259
|
const file = await createFile(filePath, content, {
|
|
251
|
-
encoding: CHAR,
|
|
260
|
+
encoding: CHAR,
|
|
261
|
+
flag: 'w',
|
|
262
|
+
mode: PERM_FILE
|
|
252
263
|
});
|
|
253
264
|
if (file && info) {
|
|
254
265
|
console.info(`Created: ${file}`);
|