webext-schema 6.0.0 → 7.1.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.
- package/.github/FUNDING.yml +13 -0
- package/.github/workflows/node.js.yml +31 -0
- package/README.md +37 -6
- package/SECURITY.md +36 -0
- package/eslint.config.mjs +46 -0
- package/index.js +9 -3
- package/modules/browser.js +39 -3
- package/modules/schema.js +3 -4
- package/modules/update.js +0 -5
- package/package.json +34 -25
- package/schemas/beta/webext.json +697 -17
- package/schemas/central/webext.json +694 -14
- package/schemas/mail/mailext.json +838 -104
- package/test/browser.test.js +3039 -0
- package/test/commander.test.js +134 -0
- package/test/common.test.js +120 -0
- package/test/constant.test.js +34 -0
- package/test/file/test 2.cmd +2 -0
- package/test/file/test 2.sh +2 -0
- package/test/file/test.cmd +2 -0
- package/test/file/test.js +1 -0
- package/test/file/test.sh +2 -0
- package/test/file/test.txt +1 -0
- package/test/file-util.test.js +370 -0
- package/test/index.test.js +59 -0
- package/test/mocha/setup.js +40 -0
- package/test/schema.test.js +1280 -0
- package/test/update.test.js +1054 -0
- 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 -11
- package/schemas/esr/webext.json +0 -21462
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# These are supported funding model platforms
|
|
2
|
+
|
|
3
|
+
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
4
|
+
patreon: # Replace with a single Patreon username
|
|
5
|
+
open_collective: # Replace with a single Open Collective username
|
|
6
|
+
ko_fi: # Replace with a single Ko-fi username
|
|
7
|
+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
8
|
+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
9
|
+
liberapay: # Replace with a single Liberapay username
|
|
10
|
+
issuehunt: # Replace with a single IssueHunt username
|
|
11
|
+
otechie: # Replace with a single Otechie username
|
|
12
|
+
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
13
|
+
custom: ['https://paypal.me/asamuzakjp'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
|
2
|
+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
|
3
|
+
|
|
4
|
+
name: build
|
|
5
|
+
permissions:
|
|
6
|
+
contents: read
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches: [ main ]
|
|
11
|
+
pull_request:
|
|
12
|
+
branches: [ main ]
|
|
13
|
+
release:
|
|
14
|
+
types: [ created ]
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
strategy:
|
|
19
|
+
matrix:
|
|
20
|
+
node-version: [ 22.x, lts/*, latest ]
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v6
|
|
24
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
25
|
+
uses: actions/setup-node@v6
|
|
26
|
+
with:
|
|
27
|
+
node-version: ${{ matrix.node-version }}
|
|
28
|
+
check-latest: true
|
|
29
|
+
- run: npm ci
|
|
30
|
+
- run: npm run lint
|
|
31
|
+
- run: npm run test:all
|
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,36 @@
|
|
|
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
|
+
The following versions of this project are currently supported with security updates:
|
|
8
|
+
|
|
9
|
+
| Version | Supported |
|
|
10
|
+
| ------- | ------------------ |
|
|
11
|
+
| 7.x.x | :white_check_mark: |
|
|
12
|
+
| 6.x.x | :white_check_mark: |
|
|
13
|
+
| < 6.0.0 | :x: |
|
|
14
|
+
|
|
15
|
+
## Reporting a Vulnerability
|
|
16
|
+
|
|
17
|
+
Please **DO NOT** open a public issue for security-related vulnerabilities.
|
|
18
|
+
|
|
19
|
+
If you discover a security vulnerability, please report it privately via **GitHub's Private vulnerability reporting** feature.
|
|
20
|
+
You can do this by navigating to the **Security** tab of this repository, selecting **Advisories**, and clicking the **Report a vulnerability** button.
|
|
21
|
+
|
|
22
|
+
To help us resolve the issue efficiently, please include the following details in your report:
|
|
23
|
+
|
|
24
|
+
* **Type of vulnerability** (e.g., XSS, SSRF, Prototype Pollution)
|
|
25
|
+
* **Affected component or file name**
|
|
26
|
+
* **Steps to reproduce** (including proof of concept / PoC code if available)
|
|
27
|
+
* **Potential impact** of the vulnerability
|
|
28
|
+
|
|
29
|
+
### Our Response Process
|
|
30
|
+
|
|
31
|
+
1. **Acknowledgment**: We will acknowledge receipt of your report within 48 to 72 hours.
|
|
32
|
+
2. **Investigation**: We will verify the vulnerability and evaluate its severity.
|
|
33
|
+
3. **Fix & Release**: We will work on a fix in a private fork and release a patched version as soon as possible.
|
|
34
|
+
4. **Advisory**: A GitHub Security Advisory will be published if necessary, giving you full credit for the discovery (unless you prefer to remain anonymous).
|
|
35
|
+
|
|
36
|
+
Please adhere to Coordinated Vulnerability Disclosure (CVD) and **do not disclose the vulnerability publicly** until a patch has been released.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import jsdoc from 'eslint-plugin-jsdoc';
|
|
2
|
+
import regexp from 'eslint-plugin-regexp';
|
|
3
|
+
import unicorn from 'eslint-plugin-unicorn';
|
|
4
|
+
import globals from 'globals';
|
|
5
|
+
import neostandard, { plugins as neostdplugins } from 'neostandard';
|
|
6
|
+
|
|
7
|
+
export default [
|
|
8
|
+
...neostandard({
|
|
9
|
+
semi: true
|
|
10
|
+
}),
|
|
11
|
+
jsdoc.configs['flat/recommended'],
|
|
12
|
+
regexp.configs['flat/recommended'],
|
|
13
|
+
{
|
|
14
|
+
languageOptions: {
|
|
15
|
+
globals: {
|
|
16
|
+
...globals.browser,
|
|
17
|
+
...globals.node,
|
|
18
|
+
...globals.webextensions
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
linterOptions: {
|
|
22
|
+
reportUnusedDisableDirectives: true
|
|
23
|
+
},
|
|
24
|
+
plugins: {
|
|
25
|
+
'@stylistic': neostdplugins['@stylistic'],
|
|
26
|
+
regexp,
|
|
27
|
+
unicorn
|
|
28
|
+
},
|
|
29
|
+
rules: {
|
|
30
|
+
'@stylistic/space-before-function-paren': ['error', {
|
|
31
|
+
anonymous: 'always',
|
|
32
|
+
asyncArrow: 'always',
|
|
33
|
+
named: 'never'
|
|
34
|
+
}],
|
|
35
|
+
'no-await-in-loop': 'error',
|
|
36
|
+
'no-use-before-define': ['error', {
|
|
37
|
+
allowNamedExports: false,
|
|
38
|
+
classes: true,
|
|
39
|
+
functions: true,
|
|
40
|
+
variables: true
|
|
41
|
+
}],
|
|
42
|
+
'prefer-object-has-own': 'error',
|
|
43
|
+
'unicorn/prefer-node-protocol': 'error'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
];
|
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
|
package/modules/schema.js
CHANGED
|
@@ -22,14 +22,13 @@ export class Schema {
|
|
|
22
22
|
* construct
|
|
23
23
|
* @param {...(string|object)} [args] - optional arguments
|
|
24
24
|
* {string} - release channel.
|
|
25
|
-
*
|
|
26
|
-
* "esr", "mail".
|
|
25
|
+
* "beta", "central", "mail".
|
|
27
26
|
* {object} - sinon config
|
|
28
27
|
*/
|
|
29
28
|
constructor(...args) {
|
|
30
29
|
const [arg1, arg2] = args;
|
|
31
30
|
this.#channel =
|
|
32
|
-
isString(arg1) &&
|
|
31
|
+
isString(arg1) && /^(?:central|mail)$/.test(arg1)
|
|
33
32
|
? arg1
|
|
34
33
|
: 'beta';
|
|
35
34
|
this._sandbox =
|
|
@@ -48,7 +47,7 @@ export class Schema {
|
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
set channel(ch) {
|
|
51
|
-
if (isString(ch) &&
|
|
50
|
+
if (isString(ch) && /^(?:central|mail|beta)$/.test(ch)) {
|
|
52
51
|
this.#channel = ch;
|
|
53
52
|
}
|
|
54
53
|
}
|
package/modules/update.js
CHANGED
|
@@ -14,7 +14,6 @@ import { createFile } from './file-util.js';
|
|
|
14
14
|
import { CHAR, INDENT } from './constant.js';
|
|
15
15
|
const DIR_CWD = process.cwd();
|
|
16
16
|
const PERM_FILE = 0o644;
|
|
17
|
-
export const ESR_VER = 115;
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* fetch text
|
|
@@ -45,9 +44,6 @@ export const getChannelUrl = channel => {
|
|
|
45
44
|
case 'central':
|
|
46
45
|
dir = 'mozilla-central/';
|
|
47
46
|
break;
|
|
48
|
-
case 'esr':
|
|
49
|
-
dir = `releases/mozilla-esr${ESR_VER}/`;
|
|
50
|
-
break;
|
|
51
47
|
case 'mail':
|
|
52
48
|
dir = 'comm-central/';
|
|
53
49
|
break;
|
|
@@ -274,7 +270,6 @@ export const updateSchemas = async (cmdOpts = {}) => {
|
|
|
274
270
|
func.push(
|
|
275
271
|
saveSchemaFile('beta', info),
|
|
276
272
|
saveSchemaFile('central', info),
|
|
277
|
-
saveSchemaFile('esr', info),
|
|
278
273
|
saveSchemaFile('mail', info)
|
|
279
274
|
);
|
|
280
275
|
}
|
package/package.json
CHANGED
|
@@ -11,51 +11,60 @@
|
|
|
11
11
|
},
|
|
12
12
|
"type": "module",
|
|
13
13
|
"exports": {
|
|
14
|
-
"
|
|
14
|
+
".": {
|
|
15
15
|
"types": "./types/index.d.ts",
|
|
16
16
|
"default": "./index.js"
|
|
17
|
-
}
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
18
19
|
},
|
|
19
|
-
"types": "types/index.d.ts",
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"camelize": "^1.0.1",
|
|
22
|
-
"commander": "^
|
|
22
|
+
"commander": "^15.0.0",
|
|
23
23
|
"decamelize": "^6.0.1",
|
|
24
24
|
"json5": "^2.2.3",
|
|
25
|
-
"sinon": "^
|
|
25
|
+
"sinon": "^22.1.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@types/firefox-webext-browser": "^
|
|
29
|
-
"@types/node": "^
|
|
30
|
-
"@types/sinon": "^
|
|
31
|
-
"c8": "^
|
|
32
|
-
"eslint": "^9.
|
|
33
|
-
"eslint-plugin-jsdoc": "^
|
|
34
|
-
"eslint-plugin-regexp": "^
|
|
35
|
-
"eslint-plugin-unicorn": "^
|
|
36
|
-
"globals": "^
|
|
37
|
-
"mocha": "^11.7.
|
|
38
|
-
"neostandard": "^0.
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
28
|
+
"@types/firefox-webext-browser": "^143.0.0",
|
|
29
|
+
"@types/node": "^26.1.1",
|
|
30
|
+
"@types/sinon": "^22.0.0",
|
|
31
|
+
"c8": "^12.0.0",
|
|
32
|
+
"eslint": "^9.39.5",
|
|
33
|
+
"eslint-plugin-jsdoc": "^63.2.2",
|
|
34
|
+
"eslint-plugin-regexp": "^3.1.1",
|
|
35
|
+
"eslint-plugin-unicorn": "^64.0.0",
|
|
36
|
+
"globals": "^17.7.0",
|
|
37
|
+
"mocha": "^11.7.6",
|
|
38
|
+
"neostandard": "^0.13.0",
|
|
39
|
+
"typescript": "^7.0.2",
|
|
40
|
+
"undici": "^8.9.0"
|
|
41
|
+
},
|
|
42
|
+
"overrides": {
|
|
43
|
+
"c8": {
|
|
44
|
+
"yargs": "^18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"eslint": {
|
|
47
|
+
"js-yaml": "^4.3.0"
|
|
48
|
+
},
|
|
49
|
+
"mocha": {
|
|
50
|
+
"diff": "^8.0.3",
|
|
51
|
+
"js-yaml": "^4.3.0"
|
|
52
|
+
},
|
|
53
|
+
"serialize-javascript": "^7.0.4"
|
|
42
54
|
},
|
|
43
55
|
"scripts": {
|
|
44
56
|
"build": "npm run tsc && npm run lint && npm run test",
|
|
45
57
|
"lint": "eslint . --fix",
|
|
46
58
|
"start": "node index",
|
|
47
59
|
"test": "npm run test:central",
|
|
60
|
+
"test:all": "npm run test:beta && npm run test:central",
|
|
48
61
|
"test:beta": "c8 mocha --require=test/mocha/setup.js --channel=beta --exit test/*.test.js",
|
|
49
62
|
"test:central": "c8 mocha --require=test/mocha/setup.js --channel=central --exit test/*.test.js",
|
|
50
|
-
"test:esr": "c8 mocha --require=test/mocha/setup.js --channel=esr --exit test/*.test.js",
|
|
51
|
-
"test:release": "c8 mocha --require=test/mocha/setup.js --channel=release --exit test/*.test.js",
|
|
52
|
-
"testall": "npm-run-all -s test:*",
|
|
53
63
|
"tsc": "node index clean --dir=types -i && npx tsc",
|
|
54
|
-
"update": "
|
|
64
|
+
"update": "npm run update:beta && npm run update:central && npm run update:mail",
|
|
55
65
|
"update:beta": "node index update --channel=beta -i",
|
|
56
66
|
"update:central": "node index update --channel=central -i",
|
|
57
|
-
"update:esr": "node index update --channel=esr -i",
|
|
58
67
|
"update:mail": "node index update --channel=mail -i"
|
|
59
68
|
},
|
|
60
|
-
"version": "
|
|
69
|
+
"version": "7.1.0"
|
|
61
70
|
}
|