pax_global_header00006660000000000000000000000064143302631030014505gustar00rootroot0000000000000052 comment=9895f35d16e2ef03d6d0c1ef619f6fbeef393a2f wikibase-edit-5.3.0/000077500000000000000000000000001433026310300142335ustar00rootroot00000000000000wikibase-edit-5.3.0/.eslintrc000066400000000000000000000026751433026310300160710ustar00rootroot00000000000000// This config file is used by eslint // See package.json scripts: lint* // Rules documentation: https://eslint.org/docs/rules/ { "root": true, "env": { "browser": true, "commonjs": true, "es6": true }, "parserOptions": { "ecmaVersion": 2018 }, "extends": [ // See https://github.com/standard/eslint-config-standard/blob/master/.eslintrc.json "standard" ], "plugins": [ "prefer-arrow" ], "rules": { "array-bracket-spacing": [ "error", "always" ], "arrow-parens": [ "error", "as-needed" ], "comma-dangle": [ "error", { "arrays": "only-multiline", "objects": "only-multiline", "imports": "only-multiline", "exports": "only-multiline", "functions": "never" }], "eqeqeq": [ "error", "smart" ], "implicit-arrow-linebreak": [ "error", "beside" ], "indent": [ "error", 2, { "MemberExpression": "off" } ], "one-var": [ "off" ], "no-var": [ "error" ], "nonblock-statement-body-position": [ "error", "beside" ], "object-curly-spacing": [ "error", "always" ], "object-shorthand": [ "error", "properties" ], "prefer-arrow/prefer-arrow-functions": [ "error" ], "prefer-arrow-callback": [ "error" ], "prefer-const": [ "error" ] }, "globals": { // Mocha globals "it": "readonly", "xit": "readonly", "describe": "readonly", "xdescribe": "readonly", "before": "readonly", "beforeEach": "readonly", } } wikibase-edit-5.3.0/.githooks/000077500000000000000000000000001433026310300161405ustar00rootroot00000000000000wikibase-edit-5.3.0/.githooks/pre-commit/000077500000000000000000000000001433026310300202145ustar00rootroot00000000000000wikibase-edit-5.3.0/.githooks/pre-commit/pre-commit000077500000000000000000000000741433026310300222170ustar00rootroot00000000000000#!/usr/bin/env bash set -eu npm run lint npm run test:unit wikibase-edit-5.3.0/.github/000077500000000000000000000000001433026310300155735ustar00rootroot00000000000000wikibase-edit-5.3.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001433026310300177565ustar00rootroot00000000000000wikibase-edit-5.3.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000007331433026310300224530ustar00rootroot00000000000000--- name: Bug report about: Create a bug report --- - wikibase-edit version: wikibase-edit-5.3.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000331433026310300217420ustar00rootroot00000000000000blank_issues_enabled: true wikibase-edit-5.3.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000001571433026310300235060ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this tool --- wikibase-edit-5.3.0/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000000741433026310300221500ustar00rootroot00000000000000--- name: Question about: Ask anything about this tool --- wikibase-edit-5.3.0/.gitignore000066400000000000000000000000361433026310300162220ustar00rootroot00000000000000node_modules config/local.js wikibase-edit-5.3.0/CHANGELOG.md000066400000000000000000000276621433026310300160610ustar00rootroot00000000000000# CHANGELOG *versions follow [SemVer](http://semver.org)* ## 5.3.0 - 2022-11-01 * Add [`badge.add`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-badges) function * Add [`badge.remove`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#remove-badges) function ## 5.2.0 - 2022-10-30 * Add [`sitelink.set`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#sitelink) function * [`entity.edit`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#edit-entity): allow to set sitelinks badges ## 5.1.0 - 2022-05-03 * [`label.set`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#set-label)/[`label.set`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#set-description): allow to remove a term by passing an empty string as value ## 5.0.0 - 2022-05-03 **BREAKING CHANGE**: updated NodeJS minimal version `>= v10.0.0` ## 4.16.0 - 2022-01-10 * Added [claim reconciliation modes](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#reconciliation) ## 4.15.0 - 2021-11-05 * Added support for the `localMedia` datatype (defined by [Extension:Wikibase_Local_Media](https://www.mediawiki.org/wiki/Extension:Wikibase_Local_Media)) ## 4.14.0 - 2021-06-01 * Added a `wgScriptPath` parameter to the [general config](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#general-config) ## 4.13.0 - 2021-04-03 * [`maxlag` parameter](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#maxlag): allow to disable for interactive tasks. ## 4.12.0 - 2021-04-03 * Quantity snaks: add support for `lowerBound` and `upperBound` ## 4.11.0 - 2020-12-14 * Allow to pass a `baserevid` ## 4.10.0 - 2020-12-02 * [`entity.edit`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#edit-entity): allow to add aliases without removing the existing ones ## 4.9.0 - 2020-12-01 * [`entity.edit`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#edit-entity): allow to remove labels, descriptions, or aliases in a given language by passing null ## 4.8.0 - 2020-10-07 * [`claim.move`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#move-claim): added capacity to [move claims between properties of different datatypes](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#move-claims-between-properties-of-different-datatypes) * [`claim.qualifier`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#move-claim): added capacity to [move qualifiers between properties of different datatypes](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#move-qualifiers-between-properties-of-different-datatypes) ## 4.7.0 - 2020-08-07 * Added support for [custom calendars](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#calendar) ## 4.6.0 - 2020-07-19 * [`claim.update`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#update-claim): added possibility to set the claim rank * [`claim.create`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#create-claim): added possibility to set the claim rank, add qualifiers and references * [`entity.edit`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#edit-entity): allow to remove a sitelink by passing null ## 4.5.0 - 2020-07-07 * [`reference.set`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#set-reference): * Added a snaks object interface to be able to set a whole reference record at once. The previous property/value interface is now deprecated. * Added the possibility to update an existing reference by specifying its current `hash` ## 4.4.0 - 2020-06-09 * Added [`qualifier.move`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#move-qualifier) * [`claim.move`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#move-claim): added [property claims mode](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#move-all-claims-from-an-entity-property) ## 4.3.0 - 2020-05-17 * Added [`claim.move`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#move-claim) ## 4.2.0 - 2020-04-08 * Added tags support * Added anonymous mode support ## 4.1.0 - 2019-12-15 * Added [`getAuthData`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#get-auth-data) function ## 4.0.0 - 2019-12-13 **Breaking Changes**: started using async/await internally, so `wikibase-edit` now requires NodeJS `>= v7.6.0`. If you are locked on an older version of NodeJS, you are thus advised to stay on `wikibase-edit@3` ## 3.2.0 - 2019-08-17 * Added [`entity.merge`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#entity-merge) function * Added [`maxlag` parameter support](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#maxlag) ## 3.1.0 - 2019-08-17 * Added [`entity.delete`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#entity-delete) function ## 3.0.0 - 2019-08-17 **Breaking Changes**: * Renamed the module `wikidata-edit` -> `wikibase-edit` * Functions now expect a unique parameter object: * ex: `claim.add(id, language, value)` should now be written `claim.add({ id, language, value })` * Aligning functions names to their associated Wikibase API actions, consequently breaking several functions: * `claim.add` => `claim.create` * `qualifier.add` => `qualifier.set` * `reference.add` => `reference.set` * config: * `instance` is now a required parameter (not defaulting to wikidata.org anymore) * credentials (username/password or oauth) are now expected to be found in a `credentials` object (see [config documentation](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#config)) * removed per-function initialization: it was meant to allow passing a different config object at every call (typically for different oauth sets of keys), which is now made possible by [passing a config object after the function parameters](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#per-request-config) * removed function `claim.exists` * removed `claim.add` claim existance check (no more `allowDuplicates` flag) **Deprecated**: * `wikibaseInstance` config parameter: renamed `instance` for consistency with other WikibaseJS modules **Added features**: * [`entity.create` now support creating properties!](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#create-property) * properties datatypes list is now generated by requesting the Wikibase instance specified in config: no more hard coded properties, no more coupling to wikidata.org! ## 2.9.0 - 2019-05-17 * [`entity.edit`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#edit-entity): allow to remove labels, descriptions, aliases, claims, or sitelinks ## 2.8.0 - 2019-01-07 * [`entity.edit`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#edit-entity)|[`entity.create`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#create-entity): add support for special snaktypes * [`entity.edit`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#edit-entity)|[`entity.create`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#create-entity): add support for special ranks ## 2.7.0 - 2018-10-02 * [`claim.add`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-claim): added an `allowDuplicates` flag to force add when a claim with the same value already exists ## 2.6.0 - 2018-08-22 * [`claim.update`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#update-claim) can now also [accepts a claim GUID](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#find-claim-to-update-by-claim-guid), instead of an entity id, a property, and a value ## 2.5.0 - 2018-08-22 * Added support for `globecoordinate` claims to [`claim.update`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#update-claim) ## 2.4.0 - 2018-08-07 * Added support for `somevalue` and `novalue` [claims](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-claim) and [qualifiers](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-qualifier) ## 2.3.0 - 2018-05-16 * Added support for [globe-coordinate](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-claim), thank to [Davide Allavena](https://github.com/DavideAllavena)'s PR ## 2.2.0 - 2018-05-07 * Added [edit summaries](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#edit-summary) ## 2.1.0 - 2018-03-03 * Added support for [more time precisions](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-claim), thank to [Riccardo Magliocchetti](https://github.com/xrmx)'s PR ## 2.0.0 - 2017-11-01 * BREAKING CHANGE: [`reference.add`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-reference) now expects an explicit reference property * BREAKING CHANGE: [`claim.add`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-claim) now can't be passed a reference: the reference needs to be added separately * BREAKING CHANGE: quantity claims with a unit can't be passed as an array of the shape `[ amount, unit ]` anymore, and should instead be passed as an object `{ amount, unit }` * BREAKING CHANGE: monolingual text can't be passed as an array of the shape `[ text, language ]` anymore, and should instead be passed as an object `{ text, language }` * Added [`reference.remove`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#remove-reference) * [`alias.add`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-alias): time claims now accept month and day precisions * Added [`qualifier.add`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-qualifier) * Added [`qualifier.update`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#update-qualifier) * Added [`qualifier.remove`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#remove-qualifier) * [`entity.edit`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#edit-entity)|[`entity.create`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#create-entity): added support for qualifiers and references ## 1.9.0 - 2017-10-13 * Added [bot edits](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#bot-edits) support ## 1.8.0 - 2017-09-11 * Added [`alias.add`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-alias) * Added [`alias.remove`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#remove-alias) * Added [`alias.set`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#set-alias) ## 1.7.0 - 2017-05-27 * Added [`claim.update`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#update-claim) ## 1.6.0 - 2017-05-22 * Allow [`per-function initialization`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#per-function-initialization) ## 1.5.0 - 2017-05-22 * Added [`OAuth support`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#config) ## 1.4.0 - 2017-05-14 * Added [`description.set`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#set-description) ## 1.3.0 - 2017-04-01 * Added [`claim.remove`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#remove-claim) ## 1.2.0 - 2017-02-20 * Added [`entity.create`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#set-label) * Added [`entity.edit`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#set-label) ## 1.1.0 - 2017-02-20 * Added [`label.set`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#set-label) * Added [`reference.add`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-reference) ## 1.0.0 - 2017-02-19 * Added [`claim.add`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#add-claim) * Added [`claim.exist`](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md#check-if-claim-exists) wikibase-edit-5.3.0/README.md000066400000000000000000000064451433026310300155230ustar00rootroot00000000000000# wikibase-edit Edit [Wikibase](https://wikiba.se) from [NodeJS](https://nodejs.org). That can be [Wikidata](https://www.wikidata.org) or whatever Wikibase instance you have. This project has received a [Wikimedia Project Grant](https://meta.wikimedia.org/wiki/Grants:Project/WikidataJS).
wikibase           wikidata
[![NPM](https://nodei.co/npm/wikibase-edit.png?stars&downloads&downloadRank)](https://npmjs.com/package/wikibase-edit/) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Node](https://img.shields.io/badge/node-%3E=%20v7.6.0-brightgreen.svg)](http://nodejs.org) [![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) [Download stats](https://npm-stat.com/charts.html?package=wikibase-edit) ## Summary - [Changelog](CHANGELOG.md) - [Install](#install) - [How-To](https://github.com/maxlath/wikibase-edit/blob/main/docs/how_to.md) - [Development setup](https://github.com/maxlath/wikibase-edit/blob/main/docs/development_setup.md) - [Contributing](#contributing) - [See Also](#see-also) - [You may also like](#you-may-also-like) - [License](#license) ## Changelog See [CHANGELOG.md](CHANGELOG.md) for version info ## Install ```sh npm install wikibase-edit ``` ## How-To See [How-to](docs/how_to.md) doc ## Development See [Development](docs/development.md) doc ## Contributing Code contributions and propositions are very welcome, here are some design constraints you should be aware of: * `wikibase-edit` focuses on exposing Wikibase write operations. Features about getting and parsing data should rather go to [`wikibase-sdk`](https://github.com/maxlath/wikibase-sdk) ## See Also * [wikibase-sdk](https://github.com/maxlath/wikibase-sdk): a javascript tool suite to query and work with any Wikibase data, heavily used by wikibase-edit and wikibase-cli * [wikibase-cli](https://github.com/maxlath/wikibase-cli): The friendly command-line interface to Wikibase * [wikibase-dump-filter](https://npmjs.com/package/wikibase-dump-filter): Filter and format a newline-delimited JSON stream of Wikibase entities * [wikidata-subset-search-engine](https://github.com/inventaire/entities-search-engine/tree/wikidata-subset-search-engine): Tools to setup an ElasticSearch instance fed with subsets of Wikidata * [wikidata-taxonomy](https://github.com/nichtich/wikidata-taxonomy): Command-line tool to extract taxonomies from Wikidata * [Other Wikidata external tools](https://www.wikidata.org/wiki/Wikidata:Tools/External_tools) ## You may also like [![inventaire banner](https://inventaire.io/public/images/inventaire-brittanystevens-13947832357-CC-BY-lighter-blue-4-banner-500px.png)](https://inventaire.io) Do you know [Inventaire](https://inventaire.io/)? It's a web app to share books with your friends, built on top of Wikidata! And its [libre software](http://github.com/inventaire/inventaire) too. ## License [MIT](LICENSE.md) wikibase-edit-5.3.0/config/000077500000000000000000000000001433026310300155005ustar00rootroot00000000000000wikibase-edit-5.3.0/config/default.js000066400000000000000000000012661433026310300174670ustar00rootroot00000000000000// Log full objects require('util').inspect.defaultOptions.depth = null require('module-alias/register') module.exports = { // initConfig instance: 'http://localhost:8181', credentials: { oauth: { consumer_key: 'some-consumer-token', consumer_secret: 'some-secret-token', token: 'some-user-token', token_secret: 'some-secret-token' } }, // Used for testing that both means of login work // but can be inverted or disabled if you can't get owner-only oauth tokens credentialsAlt: { username: 'some-username', password: 'some-password' }, secondUserCredentials: { username: 'another-username', password: 'another-password' } } wikibase-edit-5.3.0/docs/000077500000000000000000000000001433026310300151635ustar00rootroot00000000000000wikibase-edit-5.3.0/docs/development_setup.md000066400000000000000000000072051433026310300212530ustar00rootroot00000000000000# Development setup Development setup is mostly about getting confortable with [automated tests](https://en.wikipedia.org/wiki/Test_automation): it's nice to add new features, it's better to know that those features won't be broken or removed by mistake. This can be done by adding automated tests: those tests will be run before publishing any new version to guarantee that the new version doesn't introduce any regression. ## Summary - [Install tests dependencies](#install-tests-dependencies) - [Unit tests](#unit-tests) - [Run a single unit test file](#run-a-single-unit-test-file) - [Run all the unit tests](#run-all-the-unit-tests) - [Integration tests](#integration-tests) - [Setup a test Wikibase instance](#setup-a-test-wikibase-instance) - [Use test.wikidata.org](#use-testwikidataorg) - [Install a local Wikibase with Docker](#install-a-local-wikibase-with-docker) - [Run a single integration test file](#run-a-single-integration-test-file) - [Run all the integration tests](#run-all-the-integration-tests) ## Install tests dependencies ```sh npm install ``` This will install the dependencies we need to run tests, especially: * [mocha](https://mochajs.org/): the executable to which we pass test files, and that defines the following global functions used in test files: `describe`, `it`, `beforeEach`, etc * [should.js](https://shouldjs.github.io/): a lib to easily make assertions and throw errors when those assertions aren't true: ```js (12).should.be.above(10) // will not throw (12).should.be.below(10) // will throw and thus make the test fail ``` ## Unit tests [Unit tests](https://en.wikipedia.org/wiki/Unit_testing) are used to test a single function at once, and can be run without any other setup. ### Run a single unit test file ```sh ./node_modules/.bin/mocha ./tests/unit/parse_instance.js ``` Just try to run it, you can't break anything! And then try to modify the function it tests, `lib/parse_instance.js`, and see how that makes the tests fail. To run only one test in that file, replace `it(` by `it.only(` ### Run all the unit tests ```sh npm run test:unit ``` ## Integration tests [Integration tests](https://en.wikipedia.org/wiki/Integration_testing) are used to check that the function produce the desired behaviour on a Wikibase instance. We thus need to have a Wikibase instance at hand to run our functions against, thus the more elaborated setup. ### Setup a test Wikibase instance Create a `./config/local.js` file overriding values in `./config/default.js` with your credentials on the Wikibase instance you want to use: either [test.wikidata.org](https://test.wikidata.org) or your own local Wikibase. #### Use [test.wikidata.org](https://test.wikidata.org) That's the easiest option. **Pros**: * zero setup **Cons**: * tests are slowed down by network latency * doesn't work when you're offline/on a bad connection Tests should pass as any user can create properties on that instance. That's probably the easiest setup to get started. #### Install a local Wikibase with Docker ```sh git clone https://github.com/wmde/wikibase-docker cd wikibase-docker docker-compose up -d wikibase ``` See [`Docker documentation`](https://docs.docker.com/compose/install/) ### Run a single integration test file ```sh ./node_modules/.bin/mocha ./tests/integration/label/set.js ``` To run only one test in that file, replace `it(` by `it.only(` ### Run all the integration tests ```sh npm run test:integration ``` wikibase-edit-5.3.0/docs/how_to.md000066400000000000000000001210631433026310300170070ustar00rootroot00000000000000# How-To ## Summary - [Config](#config) - [General config](#general-config) - [Per-request config](#per-request-config) - [Credentials](#credentials) - [Single-user setup](#single-user-setup) - [Using your username and password](#using-your-username-and-password) - [Using your username and a bot password:](#using-your-username-and-a-bot-password) - [Owner-only OAuth consumer:](#owner-only-oauth-consumer) - [Multi-user setup](#multi-user-setup) - [Bot](#bot) - [Maxlag](#maxlag) - [API](#api) - [Label](#label) - [set label](#set-label) - [Description](#description) - [set description](#set-description) - [Alias](#alias) - [add aliases](#add-aliases) - [remove aliases](#remove-aliases) - [set aliases](#set-aliases) - [Claim](#claim) - [create claim](#create-claim) - [monolingualtext](#monolingualtext) - [quantity](#quantity) - [time](#time) - [precision](#precision) - [calendar](#calendar) - [globe-coordinate](#globe-coordinate) - [Special snaktypes](#special-snaktypes) - [update claim](#update-claim) - [find claim to update by value](#find-claim-to-update-by-value) - [find claim to update by claim guid](#find-claim-to-update-by-claim-guid) - [update rank](#update-rank) - [move claim](#move-claim) - [move a single claim](#move-a-single-claim) - [move all claims from an entity property](#move-all-claims-from-an-entity-property) - [move claims between properties of different datatypes](#move-claims-between-properties-of-different-datatypes) - [remove claim](#remove-claim) - [Qualifier](#qualifier) - [set qualifier](#set-qualifier) - [update qualifier](#update-qualifier) - [move qualifier](#move-qualifier) - [move a unique qualifier](#move-a-unique-qualifier) - [move all qualifiers from a property to another](#move-all-qualifiers-from-a-property-to-another) - [move qualifiers between properties of different datatypes](#move-qualifiers-between-properties-of-different-datatypes) - [remove qualifier](#remove-qualifier) - [Reference](#reference) - [set reference](#set-reference) - [remove reference](#remove-reference) - [Sitelink](#sitelink) - [add or update a sitelink](#add-or-update-a-sitelink) - [add or update a sitelink with badges](#add-or-update-a-sitelink-with-badges) - [remove a sitelink](#remove-a-sitelink) - [Badges](#badges) - [add badges](#add-badges) - [remove badges](#remove-badges) - [Entity](#entity) - [edit entity](#edit-entity) - [incremental mode](#incremental-mode) - [reset mode](#reset-mode) - [create entity](#create-entity) - [create item](#create-item) - [create property](#create-property) - [merge entity](#merge-entity) - [merge item](#merge-item) - [merge property](#merge-property) - [delete entity](#delete-entity) - [delete item](#delete-item) - [delete property](#delete-property) - [get auth data](#get-auth-data) - [Reconciliation](#reconciliation) - [matching](#matching) - [claim matching](#claim-matching) - [reference matching](#reference-matching) - [reconciliation modes](#reconciliation-modes) - [skip-on-any-value mode](#skip-on-any-value-mode) - [skip-on-value-match mode](#skip-on-value-match-mode) - [merge mode](#merge-mode) - [Tips](#tips) - [How to get a claim guid](#how-to-get-a-claim-guid) ## Config ### General config If all your edits are made on the same Wikibase instance with the same credentials, simply pass it all at initialization ```js const generalConfig = { // A Wikibase instance is required instance: 'https://www.wikidata.org', // The instance script path, used to find the API endpoint // Default: /w wgScriptPath: '/w', // One authorization mean is required (unless in anonymous mode, see below) credentials: { // either a username and password username: 'my-wikidata-username', // Optional: generate a dedicated password with tailored rights on /wiki/Special:BotPasswords // See the 'Credentials' paragraph below password: 'my-wikidata-password', // OR OAuth tokens oauth: { // Obtained at registration // https://www.mediawiki.org/wiki/OAuth/For_Developers#Registration consumer_key: 'your-consumer-token', consumer_secret: 'your-secret-token', // Obtained when the user authorized your service // see https://www.mediawiki.org/wiki/OAuth/For_Developers#Authorization token: 'a-user-token', token_secret: 'a-secret-token' } }, // Flag to activate the 'anonymous' mode, // which actually isn't anonymous as it signs with your IP // Default: false anonymous: true, // Optional // See https://meta.wikimedia.org/wiki/Help:Edit_summary // Default: empty summary: 'some edit summary common to all the edits', // See https://www.mediawiki.org/wiki/Manual:Tags // Default: on Wikidata [ 'WikibaseJS-edit' ], empty for other Wikibase instances tags: [ 'Some general tag' ], // Default: `wikidata-edit/${pkg.version} (https://github.com/maxlath/wikidata-edit)` userAgent: 'my-project-name/v3.2.5 (https://project.website)', // See https://www.mediawiki.org/wiki/Manual:Bots // Default: false bot: true, // See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter // Default: 5 maxlag: 2 } const wbEdit = require('wikibase-edit')(generalConfig) wbEdit.label.set({ id, language, value }) ``` So if you have a local Wikibase instance installed via [`wikibase-docker`](https://github.com/wmde/wikibase-docker) with the default settings, that would look something like: ```js const generalConfig = { instance: 'http://localhost:8181', credentials: { username: 'WikibaseAdmin', password: 'WikibaseDockerAdminPass' } } const wbEdit = require('wikibase-edit')(generalConfig) ``` ### Per-request config If you make requests to different Wikibase instances or with different credentials (typically when using different users OAuth keys), you can pass those specific configuration parameter per function call: ```js const generalConfig = { userAgent } const wbEdit = require('wikibase-edit')(generalConfig) const requestConfig = { instance: 'https://project-915215.wikibase.farm', credentials: { // Either a username and password username, password, // OR oauth }, // Default: false anonymous: false, // See https://www.mediawiki.org/wiki/Manual:Bots // Default: false bot: true, // See https://meta.wikimedia.org/wiki/Help:Edit_summary // Default: empty summary: 'some request specific edit summary', // An id for the last known revision that can be passed per request so that the server can detect edit collisions // That id can be found in any edit response as `lastrevid` // See https://www.mediawiki.org/wiki/Wikibase/API#Request_Format baserevid: 1234, // See https://www.mediawiki.org/wiki/Manual:Tags // Default: on Wikidata [ 'WikibaseJS-edit' ], empty for other Wikibase instances tags: [ 'Some edit specific tag' ], // See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter // Default: 5 maxlag: 5 } wbEdit.label.set({ id, language, value }, requestConfig) ``` Rules: * All parameters can either be set in `generalConfig` or `requestConfig` ### Credentials #### Single-user setup Different options are available to authentify your requests as a single user. (That's typically what you need unless you are building a web service where multiple users would be making edits in their name. If you are building this kind of web service, see [Multi-user setup](#multi-user-setup)). ##### Using your username and password * That's the least secured way to do it but perfectly fine for prototyping on your local Wikibase instance * :warning: This method is using the [`API:Login` endpoint](https://www.mediawiki.org/wiki/API:Login), which has been deprecated for a while: for a safer and more long term solution, prefer using an Owner-only OAuth consumer (see below) * Your [config object](#general-config) would then look something like: ```js const generalConfig = { instance: 'http://localhost:8181', credentials: { username: 'TestWikibaseUser', password: 'TestWikibaseUserPassword' } } ``` ##### Using your username and a bot password: * Any user (not just [bot accounts](https://www.wikidata.org/wiki/Wikidata:Bots) despite the name) can generate passwords with restricted rights. Recommanded grants: * `Edit existing pages` * `Delete pages, revisions, and log entries` (Optional, required by `wbEdit.entity.delete`) * :warning: This method is also using the [`API:Login` endpoint](https://www.mediawiki.org/wiki/API:Login), which has been deprecated for a while: for a more long term solution, prefer using an Owner-only OAuth consumer (see below) * Your [config object](#general-config) would then look something like: ```js const generalConfig = { instance: 'https://www.somewikibase.instance', credentials: { username: 'MyUsername', password: 'password_name@jkvbxgq9xiu16yb8vgzjp4gtd6m258os' } } ``` ##### [Owner-only OAuth consumer](https://www.mediawiki.org/wiki/OAuth/Owner-only_consumers): * Can be created without the validation of an administrator on `/wiki/Special:OAuthConsumerRegistration/propose?wpownerOnly=1`, but **not all users have the permission to do it** (if that's your case, you should probably use the bot password method above). * For Wikimedia instances (`www.wikidata.org`, `test.wikidata.org`), consumer registration is done on meta: https://meta.wikimedia.org/wiki/Special:OAuthConsumerRegistration/propose?wpownerOnly=1 * Requires that your Wikibase instance has installed the [Extension:OAuth](https://www.mediawiki.org/wiki/Extension:OAuth#User_rights) (you can check that on `/wiki/Special:Version`). * Your [config object](#general-config) would then look something like: ```js const generalConfig = { instance: 'https://www.somewikibase.instance', credentials: { oauth: { 'consumer_key': 'c60acb4f8abfa667ea5bafcdb2b673c7', 'consumer_secret': '1adbc98303a0b5b03311ebeee80d6916cbe1bd1f', 'token': '8de15abb42b8f9f15444ee4f13bb1f3d', 'token_secret': '2ba1e72cb947adda5da196d5d2cc57adf12aeaec' } } } ``` #### Multi-user setup If you are running a web service that lets people other than yourself make edits on a Wikibase instance, using their usernames and passwords isn't an option: you're only option is to setup an [OAuth consumer](https://www.mediawiki.org/wiki/OAuth/For_Developers). That's for example what inventaire.io uses to allow users to make edits on wikidata.org in their name. * First, you need to request an OAuth consumer on the desired Wikibase instance: * For Wikimedia instances (`www.wikidata.org`, `test.wikidata.org`), consumer registration is done on meta: https:// meta.wikimedia.org/wiki/Special:OAuthConsumerRegistration/propose * For other Wikibase instance, see `/wiki/Special:OAuthConsumerRegistration/propose` * Then you will need to setup an OAuth authentification process to allow users of your web service to authorize your consumer to make edits in their name. That's the tricky part. Some libraries should help you to do that, such as [`passport-mediawiki-oauth`](https://www.npmjs.com/package/passport-mediawiki-oauth) (see [Help:Toolforge/My first NodeJS OAuth tool](https://wikitech.wikimedia.org/wiki/Help:Toolforge/My_first_NodeJS_OAuth_tool)), but some people just prefer rolling their own (ex: [inventaire.io implementation](https://github.com/inventaire/inventaire/blob/3dbec57/server/controllers/auth/wikidata_oauth.coffee)). (If you go for this later option, :warning: beware of the documentation [footnotes](https://www.mediawiki.org/wiki/OAuth/For_Developers#Notes): make sure to use the right URLs before loosing hours at a `invalid signature` error message). * As your requests might now be done in the name of different users each time, you will the need to pass the credentials in the [request config objects](#request-config) rather than the [general config objects](#general-config): ```js // At initialization const generalConfig = { instance: 'https://www.somewikibase.instance', credentials: { oauth: { 'token': '8de15abb42b8f9f15444ee4f13bb1f3d', 'token_secret': '2ba1e72cb947adda5da196d5d2cc57adf12aeaec' } } } const oauthConsumer = { 'consumer_key': 'c60acb4f8abfa667ea5bafcdb2b673c7', 'consumer_secret': '1adbc98303a0b5b03311ebeee80d6916cbe1bd1f', } const wbEdit = require('wikibase-edit')(generalConfig) // Later, when a user with OAuth tokens already setup (see previous step) makes an edit request const requestOauth = { 'consumer_key': oauthConsumer['consumer_key'], 'consumer_secret': oauthConsumer['consumer_secret'], 'token': user.oauth['token'], 'token_secret': user.oauth['token_secret'] } const requestConfig = { credentials: { oauth: requestOauth } } wbEdit.label.set({ id, language, value }, requestConfig) ``` ### Bot The `bot` flag will mark your edits as made by a [bot account](https://www.wikidata.org/wiki/Wikidata:Bots) ### Maxlag See [`maxlag` parameter documentation](https://www.mediawiki.org/wiki/Manual:Maxlag_parameter). If the Wikibase server returns a `maxlag` error, the request will automatically be re-executed after the amount of seconds recommended by the Wikibase server via the `Retry-After` header. This automatic retry can be disabled by setting `autoRetry` to `false` in the general config or the request config. As specified in the MediaWiki documentation *Interactive tasks (where a user is waiting for the result) may omit the maxlag parameter*. To do so, set `maxlag = null` in the config object. ## API All functions return promises. See also [Wikidata API documentation](https://www.wikidata.org/w/api.php). ### Label #### set label ```js wbEdit.label.set({ id: 'Q4115189', language: 'fr', value: 'Bac à sable bulgroz' }) ``` This can also be used to remove a label ```js wbEdit.label.set({ id: 'Q4115189', language: 'fr', value: '' }) ``` ### Description #### set description ```js wbEdit.description.set({ id: 'Q4115189', language: 'fr', value: 'description du Bac à sable bulgroz' }) ``` This can also be used to remove a description ```js wbEdit.description.set({ id: 'Q4115189', language: 'fr', value: '' }) ``` ### Alias #### add aliases ```js // Add one alias wbEdit.alias.add({ id: 'Q4115189', language: 'fr', value: 'foo' }) // Add several aliases wbEdit.alias.add({ id: 'Q4115189', language: 'fr', value: [ 'foo', 'bar' ] }) ``` #### remove aliases ```js // Remove one alias wbEdit.alias.remove({ id: 'Q4115189', language: 'fr', value: 'foo' }) // Remove several aliases wbEdit.alias.remove({ id: 'Q4115189', language: 'fr', value: [ 'foo', 'bar' ] }) ``` #### set aliases ```js // Replace the current list of aliases in French on Q4115189 by 'foo' wbEdit.alias.set({ id: 'Q4115189', language: 'fr', value: 'foo' }) // Replace the current list of aliases in French on Q4115189 by 'foo' and 'bar' wbEdit.alias.set({ id: 'Q4115189', language: 'fr', value: [ 'foo', 'bar'] }) ``` ### Claim #### create claim **NB**: **By default, no check is performed to see if the claims values already exist**. To perform this kind of check and avoid creating duplicated claims, see the section on **[reconciliation](#reconciliation)**. ```js // Simplest case wbEdit.claim.create({ id: 'Q4115189', property: 'P2002', value: 'bulgroz' }) // With a datatype requiring a rich value (see below for the different datatypes) wbEdit.claim.create({ id: 'Q4115189', property: 'P1476', value: { text: 'bulgroz', language: 'it' } }) // Advanced case with a rank, qualifiers, and references wbEdit.claim.create({ id: 'Q4115189', property: 'P2002', value: 'bulgroz', rank: 'preferred', qualifiers: { P370: 'foo' }, references: [ { P143: 'Q8447', P813: '2020-07' }, { P143: 'https://some.source/url', P813: '2020-07-19' } ] }) ``` ##### monolingualtext ```js // Monolingualtext property wbEdit.claim.create({ id: 'Q4115189', property: 'P1476', value: { text: 'bulgroz', language: 'it' } }) ``` ##### quantity ```js // Quantity: // pass a single value for a count without a specific unit wbEdit.claim.create({ id: 'Q4115189', property: 'P1106', value: 9000 }) // pass an object for a value with a specific unit. Example here to specify minutes (Q7727) wbEdit.claim.create({ id: 'Q4115189', property: 'P2097', value: { amount: 9000, unit: 'Q7727', lowerBound: 9000, upperBound: 9315 } }) ``` ##### time ```js wbEdit.claim.create({ id: 'Q4115189', property: 'P569', value: '1802-02-26' }) ``` ###### precision *see https://www.wikidata.org/wiki/Help:Dates#Precision* By default, the precision is inferred from the input ```js // Inferred year precision (9) wbEdit.claim.create({ id, property, value: '1802' }) // Inferred month precision (10) wbEdit.claim.create({ id, property, value: '1802-02' }) // Inferred day precision (11) wbEdit.claim.create({ id, property, value: '1802-02-26' }) ``` But in some cases it needs to be explicitly specified: ```js // billion years wbEdit.claim.create({ id, property, value: { time: '-13000000000', precision: 0 }}) // million years wbEdit.claim.create({ id, property, value: { time: '-1000000', precision: 3 }}) // hundred thousand years wbEdit.claim.create({ id, property, value: { time: '-100000', precision: 4 }}) // ten thousand years wbEdit.claim.create({ id, property, value: { time: '-50000', precision: 5 }}) // millennium wbEdit.claim.create({ id, property, value: { time: '1000', precision: 6 }}) // century wbEdit.claim.create({ id, property, value: { time: '1800', precision: 7 }}) // decade wbEdit.claim.create({ id, property, value: { time: '1800', precision: 8 }}) // year wbEdit.claim.create({ id, property, value: { time: '1802', precision: 9 }}) // month wbEdit.claim.create({ id, property, value: { time: '1802-02', precision: 10 }}) // day wbEdit.claim.create({ id, property, value: { time: '1802-02-26', precision: 11 }}) ``` ###### calendar Only 2 calendar are currently supported by Wikibase: Gregorian (Q1985727) and Julian (Q1985786) ```js // Default calendar: Gregorian wbEdit.claim.create({ id, property, value: '1802-02-26' }) // Use an object value to set a custom calendar. All the following options are valid: wbEdit.claim.create({ id, property, value: { time: '0302-05-01', calendar: 'julian' }}) wbEdit.claim.create({ id, property, value: { time: '0302-05-01', calendar: 'Q1985786' }}) wbEdit.claim.create({ id, property, value: { time: '0302-05-01', calendar: 'http://www.wikidata.org/entity/Q1985786' }}) wbEdit.claim.create({ id, property, value: { time: '0302-05-01', calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }}) ``` ##### globe-coordinate ```js // with a precision of an arcsecond wbEdit.claim.create({ id: 'Q4115189', property: 'P626', value: { latitude: 45.758, longitude: 4.84138, precision: 1 / 360 } }) ``` ##### Special snaktypes ```js // somevalue wbEdit.claim.create({ id: 'Q4115189', property: 'P19', value: { snaktype: 'somevalue' } }) // novalue wbEdit.claim.create({ id: 'Q4115189', property: 'P27', value: { snaktype: 'novalue' } }) ``` #### update claim A function to change the value of an existing claim without having to remove it and while keeping its references and qualifiers. ##### find claim to update by value ```js wbEdit.claim.update({ id: 'Q4115189', property: 'P2002', oldValue: 'initial-value', newValue: 'new-value' }) ``` It will return a rejected promise if several claims with the same value already exist. It can also be used for rich values such as `globecoordinate` claims: ```js const oldValue = { latitude: 18.65, longitude: 226.2, precision: 0.01, globe: "http://www.wikidata.org/entity/Q111" } const newValue = { latitude: 18.65, longitude: 226.2, precision: 0.01, globe: "http://www.wikidata.org/entity/Q313" } wbEdit.claim.update({ id: 'Q4115189', property: 'P2002', oldValue, newValue }) ``` ##### find claim to update by claim guid Instead of passing the old value, you can pass the [claim guid](#how-to-get-a-claim-guid). That's generally considered a more reliable approach. ```js const guid = 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F' wbEdit.claim.update({ guid, newValue: 'new-value' }) ``` ##### update rank It is possible to set the claim rank along setting a new value ```js wbEdit.claim.update({ guid, rank: 'preferred', newValue: 'new-value' }) ``` It is also possible to change the claim rank alone ```js wbEdit.claim.update({ guid, rank: 'preferred' }) ``` #### move claim Move a claim from an entity to another and/or from a property to another This function requires to know about [claim guid](#how-to-get-a-claim-guid). ##### move a single claim * change the property of a claim (without changing entity) ```js const wbEdit.claim.move({ // This guid identifies a P19 claim on Q4115189 guid: 'Q4115189$13681798-47F7-4D51-B3B4-BA8C7E044E1F', id: 'Q4115189', property: 'P20' }) ``` * move the claim to another entity (without changing the property) ```js const wbEdit.claim.move({ // This guid identifies a P19 claim on Q4115189 guid: 'Q4115189$13681798-47F7-4D51-B3B4-BA8C7E044E1F', id: 'Q13406268', property: 'P19' }) ``` * move the claim to another entity and another property ```js const wbEdit.claim.move({ // This guid identifies a P19 claim on Q4115189 guid: 'Q4115189$13681798-47F7-4D51-B3B4-BA8C7E044E1F', id: 'Q13406268', property: 'P20' }) ``` ##### move all claims from an entity property * change the property of all `Q4115189` `P19` claims (without changing entity) ```js const wbEdit.claim.move({ propertyClaimsId: 'Q4115189#P19' id: 'Q4115189', property: 'P20' }) ``` * move `Q4115189` `P19` claims to another entity (without changing the property) ```js const wbEdit.claim.move({ propertyClaimsId: 'Q4115189#P19' id: 'Q13406268', property: 'P19' }) ``` * move `Q4115189` `P19` claims to another entity and another property ```js const wbEdit.claim.move({ propertyClaimsId: 'Q4115189#P19' id: 'Q13406268', property: 'P20' }) ``` ##### move claims between properties of different datatypes If the origin and target properties are of different datatypes, a type conversion will be attempted in the following cases: | origin datatype | target datatype | comment | |--------------------|-------------------|-------------------------------------------------------------------------| | `external-id` | `string` | | | `monolingualtext` | `string` | :warning: the language will be lost | | `quantity` | `string` | | | `string` | `external-id` | | | `string` | `quantity` | :warning: will throw an error if the string doesn't look like a number | In other cases, an error will be thrown: if you think that another type conversion should be possible, please [open a ticket](https://github.com/maxlath/wikibase-edit/issues/new?template=feature_request.md&title=claim.move%3A%20add%20a%20%5Btype-a%5D-%3E%5Btype-b%5D%20type%20converter&body=%20) #### remove claim ```js // remove one claim const guid = 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F' wbEdit.claim.remove({ guid }) // remove several claims on the same entity const guids = [ 'Q4115189$BB467A9A-9123-4D0C-A87A-B7BF7ACD6477', 'Q4115189$D2CC0D8C-187C-40CD-8CF3-F6AAFE1496F4' ] wbEdit.claim.remove({ guid: guids }) ``` See also: [How to get a claim guid](#how-to-get-a-claim-guid) Removing claims by their `guids` is the most relyable way, but it can also convenient to just specify claims to be removed from their value: ```js await wbEdit.claim.remove({ id: 'Q4115189', property: 'P123', value: 'Q3208426' }) ``` To customize the way the claims to remove are found, a `reconciliation` object can be passed (see the section on [reconciliation](#reconciliation) for more details)). ```js await wbEdit.claim.remove({ id: 'Q4115189', property: 'P123', value: 'Q3208426', qualifiers: { P370: 'foo' }, reconciliation: { matchingQualifiers: [ 'P370' ] } }) ``` ### Qualifier Those functions require to know about [claim guid](#how-to-get-a-claim-guid). #### set qualifier ```js const guid = 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F' // entity qualifier wbEdit.qualifier.set({ guid, property: 'P155', value: 'Q4115189' }) // string qualifier wbEdit.qualifier.set({ guid, property: 'P1545', value: '123' }) // time qualifier wbEdit.qualifier.set({ guid, property: 'P580', value: '1802-02-26' }) wbEdit.qualifier.set({ guid, property: 'P580', value: { time: '1802-02-26', precision: 11 } }) // quantity qualifier wbEdit.qualifier.set({ guid, property: 'P2130', value: 13 }) // quantity qualifier with a unit wbEdit.qualifier.set({ guid, property: 'P2130', value: { amount: 123, unit: 'Q4916' } }) // monolingualtext qualifier wbEdit.qualifier.set({ guid, property: 'P3132', value: { text : "les sanglots long des violons de l'automne", language: 'fr' } }) // somevalue wbEdit.qualifier.set({ guid, property: 'P3132', value: { snaktype : 'somevalue' } }) // novalue wbEdit.qualifier.set({ guid, property: 'P3132', value: { snaktype : 'novalue' } }) ``` See also: [How to get a claim guid](#how-to-get-a-claim-guid) #### update qualifier ```js wbEdit.qualifier.update({ guid: 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F', property: 'P155', oldValue: 'Q4115189', newValue: 'Q4115190' }) // somevalue wbEdit.qualifier.update({ guid, property, oldValue, newValue: { snaktype : 'somevalue' } }) // novalue wbEdit.qualifier.update({ guid, property, oldValue, newValue: { snaktype : 'novalue' } }) ``` #### move qualifier A function to move qualifiers between properties within a claim: ##### move a unique qualifier ```js wbEdit.qualifier.move({ guid: 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F', hash: '239ef1c81ef0c24611d6d7c294d07036e82c4666', oldProperty: 'P155', newProperty: 'P156' }) ``` ##### move all qualifiers from a property to another That's exactly the same as above, just not specifying a hash ```js wbEdit.qualifier.move({ guid: 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F', oldProperty: 'P155', newProperty: 'P156' }) ``` ##### move qualifiers between properties of different datatypes This will behave in the same way as for [moving claims between properties of different datatypes](#move-claims-between-properties-of-different-datatypes) #### remove qualifier ```js const guid = 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F' // qualifierHash can be either a single hash string or an array of reference hash strings const qualifierHash = '239ef1c81ef0c24611d6d7c294d07036e82c4666' wbEdit.reference.remove({ guid, hash: qualifierHash }) ``` ### Reference Those functions require to know about [claim guid](#how-to-get-a-claim-guid). #### set reference ```js const guid = 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F' wbEdit.reference.set({ guid, snaks: { // reference url (P854) is 'https://example.org/rise-and-fall-of-the-holy-sand-box' P854: 'https://example.org/rise-and-fall-of-the-holy-sand-box' // imported from (P143) the French Wikipedia 'Q8447' P143: 'Q8447', // snaks can also have special snaktypes P370: { snaktype : 'novalue' }, // or have several values P369: [ 'Q123', { snaktype : 'somevalue' } ] } }) ``` To update an existing reference, pass its current hash value: ```js const guid = 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F' const referenceHash = '239ef1c81ef0c24611d6d7c294d07036e82c4666' wbEdit.reference.set({ guid, hash: referenceHash, snaks: { // Will override all existing snaks of that reference with this unique snak P854: 'https://example.org/rise-and-fall-of-the-holy-sand-box' } }) ``` #### remove reference ```js const guid = 'Q4115189$E66DBC80-CCC1-4899-90D4-510C9922A04F' // referenceHash can be either a single hash string or an array of reference hash strings const referenceHash = '239ef1c81ef0c24611d6d7c294d07036e82c4666' wbEdit.reference.remove({ guid, hash: referenceHash }) ``` ### Sitelink #### add or update a sitelink ```js wbEdit.sitelink.set({ id: 'Q123', site: 'frwiki', title: 'Septembre', }) ``` #### add or update a sitelink with badges ```js wbEdit.sitelink.set({ id: 'Q123', site: 'frwiki', title: 'Septembre', badges: [ 'Q17437796', 'Q17437798' ] }) ``` #### remove a sitelink ```js wbEdit.sitelink.set({ id: 'Q123', site: 'frwiki', title: null, }) ``` ### Badge Handle badges on an existing [sitelink](#sitelink) #### add badges Add badges without removing the badges that might already have been set on the sitelink ```js wbEdit.badge.add({ id: 'Q123', site: 'frwiki', badges: [ 'Q17437798', 'Q17437796' ], }) ``` #### remove badges ```js wbEdit.badge.remove({ id: 'Q123', site: 'frwiki', badges: [ 'Q17437798', 'Q17437796' ], }) ``` ### Entity #### edit entity Make many edits on an entity at once. ##### incremental mode By default, every label, description, claim, or sitelink that isn't included in the passed object will stay untouched: only those with a `remove` flag will be removed. Beware that this isn't true for qualifiers and references, which can be removed by just being omitted (see P1114 example below). **NB**: **By default, no check is performed to see if the claims values already exist**. To perform this kind of check and avoid creating duplicated claims, see the section on **[reconciliation](#reconciliation)**. ```js wbEdit.entity.edit({ // Required id: 'Q4115189', // All the rest is optional but one of labels, descriptions, aliases, claims, or sitelinks must be set labels: { // Set a label en: 'a new label in English', // Remove a label fr: null }, descriptions: { // Set a description en: 'a new description', // Remove a description fr: null }, aliases: { // Pass aliases as an array en: [ 'foo', 'bar' ], // Or a single value de: 'buzz', // /!\ for any language specified, the values you pass will overwrite the existing values, // which means that the following empty array will remove all existing aliases in French. fr: [], // To add aliases without removing existing values, you must set 'add=true' nl: [ { value: 'bul', add: true }, { value: 'groz', add: true }, ], // The same effect of clearing all aliases in a given language can be optained by passing null es: null }, claims: { // Pass values as an array P361: [ 'Q1', 'Q2' ], // Or a single value P2002: 'bulgroz', // Or a rich value object, like a monolingual text P2093: { text: 'Author Authorson', language: 'en' }, // Or even an array of mixed simple values and rich object values P1106: [ 42, { amount: 9001, unit: 'Q7727' } ], // Add statements with special snaktypes ('novalue' or 'somevalue') P626: { snaktype: 'somevalue' }, // or special rank (Default: 'normal'. Possible values: 'preferred' or 'deprecated') P6089: { rank: 'preferred', value: 123 }, // Add qualifiers and references to value objects P369: [ // Qualifier values can also be passed in those different forms { value: 'Q5111731', qualifiers: { P580: '1789-08-04' P1416: [ 'Q13406268', 'Q32844021' ], P1106: { amount: 9001, unit: 'Q7727', lowerBound: 9000, upperBound: 9315 } } }, // References can be passed as a single reference group { value: 'Q2622004', references: { P143: 'Q8447' } }, // or as multiple references groups { value: 'Q2622009', references: [ { P855: 'https://example.org', P143: 'Q8447' }, { P855: 'https://example2.org', P143: 'Q8447' } ] } ], P1114: [ // Edit an existing claim // /!\ Beware that while editing an existing claim, // anything omitted (rank, qualifiers, or references) will be omitted!! { id: 'Q4115189$BC5F4F72-5B49-4991-AB0F-5CC8D4AAB99A', value: 123 }, // Remove an existing claim { id: 'Q4115189$afc56f6c-4e91-c89d-e287-d5691aeb063a', remove: true } ] }, sitelinks: { // Set a sitelink frwiki: 'eviv bulgroz', // Remove a sitelink eswikisource: null // Set a sitelink with badges dewiki: { title: 'eviv bulgroz', badges: [ 'Q17437796', 'Q17437798' ] }, }, // For convenience, the summary and baserevid can also be passed from this edit object summary: 'doing a bunch of edits', baserevid: 1234, }) ``` ##### reset mode If the entity you are editing requires a big cleanup, instead of adding a `remove` flag to all the elements that needs to be removed, you can set the `clear` flag to `true`, which will **delete all the entity data** before adding the specified data: ```js // Remove ALL the labels, descriptions, aliases, claims, and sitelinks, and set the English label to 'Sandbox' wbEdit.entity.edit({ id: 'Q4115189', clear: true, labels: { en: 'Sandbox' } }) ``` #### create entity ##### create item Create an [item](https://www.wikidata.org/wiki/Wikidata:Glossary#Item) from scratch. The item data follow the same rules as [`wbEdit.entity.edit`](#edit-entity), simply without the `id` ```js const { entity } = await wbEdit.entity.create({ type: 'item', labels, descriptions, aliases, claims, sitelinks }) console.log('created item id', entity.id) ``` ##### create property Creating a [property](https://www.wikidata.org/wiki/Wikidata:Glossary#Property) is just like creating an item, but with a `type=property` and a `datatype` ```js const { entity } = await wbEdit.entity.create({ type: 'property', datatype: 'string', labels, descriptions, aliases, claims }) console.log('created property id', entity.id) ``` #### merge entity ##### merge item ```js // Merge Q1 into Q2, turning Q1 into a redirection wbEdit.entity.merge({ from: 'Q1', to: 'Q2' }) ``` ##### merge property Wikibase doesn't allow to merge properties #### delete entity ##### delete item ```js wbEdit.entity.delete({ id: 'Q1' }) ``` ##### delete property ```js wbEdit.entity.delete({ id: 'P1' }) ``` ### get auth data All the functions above handle authentification for you, but you can also access the auth data, that is session cookies and the currently valid [csrf token](https://www.mediawiki.org/wiki/Manual:Edit_token), using the `getAuthData` function. ```js const { cookie, token } = await wbEdit.getAuthData() ``` It can also be used as a way to validate credentials: ```js require('wikibase-edit')({ instance, credentials }).getAuthData() .then(onValidCredentials) .catch(onInvalidCredentials) ``` ## Reconciliation > :warning: *Experimental. You are invited to use with caution: test on a small sample and see if it behaves as expected. Please report any issue, counter-intuitive behavior, or missing capability.* Several functions accept a `reconciliation` object, allowing to customize how the input is matched to existing claims, and what should be done once a match is established: * [`entity.edit`](#edit-entity) * [`claim.create`](#create-claim) * [`claim.remove`](#remove-claim) ### matching #### claim matching By default, a claim is considered to be matching if its value (a.k.a. `mainsnak`) is matching. To also require to take qualifiers in consideration, when determining if a claim is matching, you can specify an array of `matchingQualifiers` properties. Example: ```js { reconciliation: { matchingQualifiers: [ 'P580', 'P582' ] // The line above is equivalent to matchingQualifiers: [ 'P580:all', 'P582:all' ] // that is that all qualifiers in the input should match qualifiers // on the existing claim for the claim to be considered a match. // To match on any qualifier for each of those properties instead, you could add the suffix `any` matchingQualifiers: [ 'P580:any', 'P582:any' ] } } ``` #### reference matching Once a claim is determined as matching (be it only from its `mainsnak` value or also taking its `qualifiers` into account), references can also be matched by specifying a `matchingReferences` array, but this is specific to references and won't influence the claim matching. It is just a way, once a matching claim is found, to avoid creating duplicated references. ```js { reconciliation: { matchingReferences: [ 'P854', 'P813' ] // The line above is equivalent to matchingQualifiers: [ 'P854:all', 'P813:all' ] // that is that all reference snaks in the input should match reference snaks // on an existing reference for the reference to be considered a match. // To match on any reference snak for each of those properties instead, you could add the suffix `any` matchingQualifiers: [ 'P854:any', 'P813:any' ] } } ``` ### reconciliation modes Once a claim is determined as matching, several modes can be used to determine what should be done with that matching claim. This also applies to matching references. #### skip-on-any-value mode * Checks if there is already a statement for that property, whatever the value * If any statement is found, no change is performed on that statement, no new statement is added either ```js wbEdit.entity.edit({ id: 'Q4115189', reconciliation: { mode: 'skip-on-any-value' }, claims: { // If Q4115189 already has a P361 claim, this won't add anything P361: [ 'Q1', ] }, }) ``` If the initial state was ```js { id: 'Q4115189', claims: { P361: [ 'Q1' ] } ``` after this edit it will still be ```js { id: 'Q4115189', claims: { P361: [ 'Q1' ] } ``` #### skip-on-value-match mode * Checks if there is a statement with the specified value * If a matching statement is found, no change is performed on that statement, no new statement is added either ```js wbEdit.entity.edit({ id: 'Q4115189', reconciliation: { mode: 'skip-on-value-match' }, claims: { // Those new values will only be added if there is no P361 statement with those values P361: [ 'Q1', 'Q2' ] }, }) ``` If the initial state was ```js { id: 'Q4115189', claims: { P361: [ 'Q1' ] } ``` after this edit it will still be ```js { id: 'Q4115189', claims: { P361: [ 'Q1' ] } ``` #### merge mode * Checks if there is a statement with the specified value * If a matching statement is found, no change is performed on that statement, missing statements will be added ```js wbEdit.entity.edit({ id: 'Q4115189', claims: { P361: [ 'Q1', 'Q2' ] }, reconciliation: { mode: 'merge' } }) ``` If the initial state was ```js { id: 'Q4115189', claims: { P361: [ 'Q1' ] } ``` after this edit it will be ```js { id: 'Q4115189', claims: { P361: [ 'Q1', 'Q2' ] } ``` ## Tips ### How to get a claim guid A globally unique identifier, or `guid` for short, is the unique identifier of a [claim](https://www.wikidata.org/wiki/Wikidata:Glossary#Claim). As such, it is used by several functions working with existing claims. While the Wikibase API sometimes just refers to those as ids, `wikibase-edit` always refer to those as guids to avoid the confusion with entities ids. But "how do I get those guids?" you may ask. There are different pathways: Maybe you got that id in a response from a previous edit: ```js const res = await wbEdit.claim.create({ id: 'Q4115189', property: 'P2002', value: 'bulgroz' }) // Here is our new claim guid, ready to be used in our next edit! const guid = res.claim.id const res2 = await wbEdit.claim.remove({ guid }, { summary: 'oops' }) // res2.claim.id === guid ``` Maybe you fetched an entity's data: ```js // Example where we try to get the guid of Q1's first P31 claim const fetch = require('node-fetch') const wbk = require('wikibase-sdk')({ instance: 'https://www.wikidata.org', sparqlEndpoint: 'https://query.wikidata.org/sparql' }) const url = wbk.getEntities({ ids: [ 'Q1' ] }) const { entities } = await fetch(url).then(res => res.json()) const firstP31Claim = entities.Q1.claims.P31[0] // Here is our guid! const guid = firstP31Claim.id ``` Maybe you got guids from a SPARQL query: ```js const sparql = 'SELECT ?statement { ?item p:P4033 ?statement . } LIMIT 5' const url = wbk.sparqlQuery(sparql) const results = await fetch(url) .then(res => res.json()) .then(wbk.simplify.sparqlResults) // Here are our statement guids! const guids = results.map(result => result.statement) ``` wikibase-edit-5.3.0/jsconfig.json000066400000000000000000000005171433026310300167330ustar00rootroot00000000000000{ "compilerOptions": { "target": "es6", "module": "commonJS", "moduleResolution": "node", "baseUrl": ".", "paths": { "lib/*": [ "./lib/*" ], "root/*": [ "*" ], "tests/*": [ "./tests/*" ], } }, "include": [ "lib/**/*", "tests/**/*" ] } wikibase-edit-5.3.0/lib/000077500000000000000000000000001433026310300150015ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/alias/000077500000000000000000000000001433026310300160725ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/alias/action.js000066400000000000000000000005311433026310300177040ustar00rootroot00000000000000const validate = require('../validate') module.exports = action => params => { const { id, language, value } = params validate.entity(id) validate.language(language) validate.aliases(value) const data = { id, language } data[action] = value instanceof Array ? value.join('|') : value return { action: 'wbsetaliases', data } } wikibase-edit-5.3.0/lib/alias/add.js000066400000000000000000000000541433026310300171570ustar00rootroot00000000000000module.exports = require('./action')('add') wikibase-edit-5.3.0/lib/alias/remove.js000066400000000000000000000000571433026310300177270ustar00rootroot00000000000000module.exports = require('./action')('remove') wikibase-edit-5.3.0/lib/alias/set.js000066400000000000000000000000541433026310300172220ustar00rootroot00000000000000module.exports = require('./action')('set') wikibase-edit-5.3.0/lib/badge/000077500000000000000000000000001433026310300160435ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/badge/add.js000066400000000000000000000013121433026310300171260ustar00rootroot00000000000000const validate = require('../validate') const format = require('../entity/format') const { getEntitySitelinks } = require('../get_entity') const error_ = require('../error') const { uniq } = require('../utils') module.exports = async (params, config, API) => { let { id, site, badges } = params validate.entity(id) validate.site(site) badges = format.badges(badges) const sitelinks = await getEntitySitelinks(id, config) const siteObj = sitelinks[site] if (!siteObj) { throw error_.new('sitelink does not exist', 400, params) } const { title, badges: currentBadges } = siteObj return API.sitelink.set({ id, site, title, badges: uniq(currentBadges.concat(badges)) }) } wikibase-edit-5.3.0/lib/badge/remove.js000066400000000000000000000014331433026310300176770ustar00rootroot00000000000000// Doc https://www.wikidata.org/w/api.php?action=help&modules=wbsetsitelink const validate = require('../validate') const format = require('../entity/format') const { getEntitySitelinks } = require('../get_entity') const error_ = require('../error') const { difference } = require('../utils') module.exports = async (params, config, API) => { let { id, site, badges } = params validate.entity(id) validate.site(site) badges = format.badges(badges) const sitelinks = await getEntitySitelinks(id, config) const siteObj = sitelinks[site] if (!siteObj) { throw error_.new('sitelink does not exist', 400, params) } const { title, badges: currentBadges } = siteObj return API.sitelink.set({ id, site, title, badges: difference(currentBadges, badges) }) } wikibase-edit-5.3.0/lib/bundle_wrapper.js000066400000000000000000000020031433026310300203430ustar00rootroot00000000000000const error_ = require('./error') const fetchUsedPropertiesDatatypes = require('./properties/fetch_used_properties_datatypes') const validateAndEnrichConfig = require('./validate_and_enrich_config') module.exports = (fn, generalConfig, API) => async (params, reqConfig) => { validateParams(params) const config = validateAndEnrichConfig(generalConfig, reqConfig) await fetchUsedPropertiesDatatypes(params, config) return fn(params, config, API) } const validateParams = params => { for (const parameter in params) { if (!validParametersKeysSet.has(parameter)) { throw error_.new(`invalid parameter: ${parameter}`, { parameter, validParametersKeys }) } } } const validParametersKeys = [ 'baserevid', 'guid', 'hash', 'id', 'newProperty', 'newValue', 'oldProperty', 'oldValue', 'property', 'propertyClaimsId', 'qualifiers', 'rank', 'reconciliation', 'references', 'summary', 'value', 'site', 'badges', ] const validParametersKeysSet = new Set(validParametersKeys) wikibase-edit-5.3.0/lib/claim/000077500000000000000000000000001433026310300160665ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/claim/builders.js000066400000000000000000000036011433026310300202350ustar00rootroot00000000000000const { getNumericId } = require('wikibase-sdk') const getTimeObject = require('./get_time_object') const { parseQuantity } = require('./quantity') // The difference in builders are due to the different expectations of the Wikibase API const singleClaimBuilders = { string: str => `"${str}"`, entity: entityId => JSON.stringify(buildEntity(entityId)), time: value => JSON.stringify(getTimeObject(value)), // Property type specific builders monolingualtext: valueObj => JSON.stringify(valueObj), quantity: (amount, instance) => JSON.stringify(parseQuantity(amount, instance)), globecoordinate: obj => JSON.stringify(obj) } const entityEditBuilders = { string: (pid, value) => valueStatementBase(pid, 'string', value), entity: (pid, value) => { return valueStatementBase(pid, 'wikibase-entityid', buildEntity(value)) }, monolingualtext: (pid, value) => { return valueStatementBase(pid, 'monolingualtext', value) }, time: (pid, value) => valueStatementBase(pid, 'time', getTimeObject(value)), quantity: (pid, value, instance) => valueStatementBase(pid, 'quantity', parseQuantity(value, instance)), globecoordinate: (pid, value) => valueStatementBase(pid, 'globecoordinate', value), specialSnaktype: (pid, snaktype) => statementBase(pid, snaktype) } const buildEntity = entityId => { entityId = entityId.value || entityId const id = getNumericId(entityId) const type = entityId[0] === 'Q' ? 'item' : 'property' return { 'entity-type': type, 'numeric-id': parseInt(id) } } const statementBase = (pid, snaktype, value) => { return { rank: 'normal', type: 'statement', mainsnak: { property: pid, snaktype } } } const valueStatementBase = (pid, type, value) => { const statement = statementBase(pid, 'value') statement.mainsnak.datavalue = { type, value } return statement } module.exports = { singleClaimBuilders, entityEditBuilders } wikibase-edit-5.3.0/lib/claim/claim_parsers.js000066400000000000000000000006731433026310300212560ustar00rootroot00000000000000const { claim: simplifyClaim } = require('wikibase-sdk').simplify const { hasSpecialSnaktype } = require('./special_snaktype') module.exports = { matchClaim: value => claim => { if (typeof value === 'object') { if (hasSpecialSnaktype(value)) { if (claim.mainsnak.snaktype === value.snaktype) return true } value = value.value } return value === simplifyClaim(claim) }, getGuid: claim => claim.id } wikibase-edit-5.3.0/lib/claim/create.js000066400000000000000000000020771433026310300176750ustar00rootroot00000000000000const error_ = require('../error') module.exports = async (params, config, API) => { const { id, property, value, qualifiers, references, rank, reconciliation } = params if (!value) throw error_.new('missing value', 400, params) const claim = { rank, qualifiers, references } if (value.snaktype && value.snaktype !== 'value') { claim.snaktype = value.snaktype } else { claim.value = value } let summary = params.summary || config.summary if (!summary) { const stringifiedValue = typeof value === 'string' ? value : JSON.stringify(value) summary = `add ${property} claim: ${stringifiedValue}` } const data = { id, claims: { [property]: claim }, summary, baserevid: params.baserevid || config.baserevid, reconciliation, } // Using wbeditentity, as the endpoint is more complete, so we need to recover the summary const { entity, success } = await API.entity.edit(data, config) const newClaim = entity.claims[property].slice(-1)[0] // Mimick claim actions responses return { claim: newClaim, success } } wikibase-edit-5.3.0/lib/claim/find_snak.js000066400000000000000000000007601433026310300203630ustar00rootroot00000000000000const error_ = require('../error') const isMatchingSnak = require('./is_matching_snak') module.exports = (property, propSnaks, searchedValue) => { if (!propSnaks) return const matchingSnaks = propSnaks.filter(snak => isMatchingSnak(snak, searchedValue)) if (matchingSnaks.length === 0) return if (matchingSnaks.length === 1) return matchingSnaks[0] const context = { property, propSnaks, searchedValue } throw error_.new('snak not found: too many matching snaks', 400, context) } wikibase-edit-5.3.0/lib/claim/format_claim_value.js000066400000000000000000000006321433026310300222560ustar00rootroot00000000000000const { parseQuantity } = require('./quantity') const { hasSpecialSnaktype } = require('./special_snaktype') module.exports = (datatype, value, instance) => { if (hasSpecialSnaktype(value)) return value // Try to recover data passed in a different type than the one expected: // - Quantities should be of type number if (datatype === 'Quantity') return parseQuantity(value, instance) return value } wikibase-edit-5.3.0/lib/claim/get_time_object.js000066400000000000000000000036201433026310300215500ustar00rootroot00000000000000const _ = require('../utils') const parseCalendar = require('./parse_calendar') module.exports = value => { let time, precision, calendar, calendarmodel if (_.isPlainObject(value)) { ({ time, precision, calendar, calendarmodel } = value) calendarmodel = calendarmodel || calendar } else { time = value } time = time // It could be a year passed as an integer .toString() // Drop milliseconds from ISO time strings as those aren't represented in Wikibase anyway // ex: '2019-04-01T00:00:00.000Z' -> '2019-04-01T00:00:00Z' .replace('.000Z', 'Z') .replace(/^\+/, '') if (precision == null) precision = getPrecision(time) const timeStringBase = getTimeStringBase(time, precision) return getPrecisionTimeObject(timeStringBase, precision, calendarmodel) } const getTimeStringBase = (time, precision) => { if (precision > 10) return time if (precision === 10) { if (time.match(/^-?\d+-\d+$/)) return time + '-00' else return time } // From the year (9) to the billion years (0) // See https://www.wikidata.org/wiki/Help:Dates#Precision const yearMatch = time.match(/^(-?\d+)/) if (yearMatch == null) throw new Error(`couldn't identify year: ${time}`) const year = yearMatch[0] return year + '-00-00' } // Guess precision from time string // 2018 (year): 9 // 2018-03 (month): 10 // 2018-03-03 (day): 11 const getPrecision = time => { const unsignedTime = time.replace(/^-/, '') return unsignedTime.split('-').length + 8 } const getPrecisionTimeObject = (time, precision, calendarmodel) => { const sign = time[0] // The Wikidata API expects signed years // Default to a positive year sign if (sign !== '-' && sign !== '+') time = `+${time}` if (precision <= 11 && !time.match('T')) time += 'T00:00:00Z' return { time, timezone: 0, before: 0, after: 0, precision, calendarmodel: parseCalendar(calendarmodel, time) } } wikibase-edit-5.3.0/lib/claim/helpers.js000066400000000000000000000010531433026310300200650ustar00rootroot00000000000000const _ = require('../utils') const { simplify } = require('wikibase-sdk') const simplifyOptions = { keepIds: true, keepSnaktypes: true, keepQualifiers: true, keepReferences: true, keepRanks: true, keepRichValues: true } module.exports = { findClaimByGuid: (claims, guid) => { for (const claim of _.flatten(_.values(claims))) { if (claim.id.toLowerCase() === guid.toLowerCase()) return claim } }, isGuidClaim: guid => claim => claim.id === guid, simplifyClaimForEdit: claim => simplify.claim(claim, simplifyOptions) } wikibase-edit-5.3.0/lib/claim/is_matching_claim.js000066400000000000000000000025041433026310300220570ustar00rootroot00000000000000const isMatchingSnak = require('./is_matching_snak') module.exports = (newClaim, matchingQualifiers) => existingClaim => { const { mainsnak, qualifiers = {} } = existingClaim if (!isMatchingSnak(mainsnak, newClaim.mainsnak)) return false if (matchingQualifiers) { for (const property of matchingQualifiers) { const [ pid, option = 'all' ] = property.split(':') if (newClaim.qualifiers[pid] != null && qualifiers[pid] == null) return false if (newClaim.qualifiers[pid] == null && qualifiers[pid] != null) return false const propertyQualifiersMatch = matchFunctions[option](newClaim.qualifiers[pid], qualifiers[pid]) if (!propertyQualifiersMatch) return false } } return true } const matchFunctions = { all: (newPropertyQualifiers, existingPropertyQualifiers) => { for (const newQualifier of newPropertyQualifiers) { for (const existingQualifier of existingPropertyQualifiers) { if (!isMatchingSnak(existingQualifier, newQualifier)) return false } } return true }, any: (newPropertyQualifiers, existingPropertyQualifiers) => { for (const newQualifier of newPropertyQualifiers) { for (const existingQualifier of existingPropertyQualifiers) { if (isMatchingSnak(existingQualifier, newQualifier)) return true } } return false }, } wikibase-edit-5.3.0/lib/claim/is_matching_snak.js000066400000000000000000000131271433026310300217310ustar00rootroot00000000000000const { snak: simplifySnak } = require('wikibase-sdk').simplify const error_ = require('../error') const { inviteToOpenAFeatureRequest } = require('../issues') const _ = require('../utils') const { parseUnit } = require('./quantity') module.exports = (existingSnak, searchedValue) => { // Support both statements and qualifiers snaks existingSnak = existingSnak.mainsnak ? existingSnak.mainsnak : existingSnak // Support both a full snak object or just the datavalue.value object searchedValue = searchedValue.datavalue ? searchedValue.datavalue.value : searchedValue const { datatype } = existingSnak if (searchedValue.snaktype && (searchedValue.snaktype !== 'value' || existingSnak.snaktype !== 'value')) { return existingSnak.snaktype === searchedValue.snaktype } if (comparatorsByDatatype[datatype] == null) { const context = { datatype } const featureRequestMessage = inviteToOpenAFeatureRequest({ title: `claim reconciliation: add support for ${datatype} datatype`, context }) throw error_.new(`unsupported datatype: ${datatype}\n${featureRequestMessage}`, context) } return comparatorsByDatatype[datatype](existingSnak, searchedValue) } const simpleValueComparison = (snak, searchedValue) => snak.datavalue.value === searchedValue const entityValueComparison = (snak, searchedValue) => { const { value } = snak.datavalue if (typeof searchedValue === 'string') { return value.id === searchedValue } else if (value.id != null && searchedValue.id != null) { return value.id === searchedValue.id } else { return value['entity-type'] === searchedValue['entity-type'] && value['numeric-id'] === searchedValue['numeric-id'] } } const comparatorsByDatatype = { commonsMedia: simpleValueComparison, 'external-id': simpleValueComparison, 'geo-shape': simpleValueComparison, 'globe-coordinate': (snak, searchedValue) => { const { latitude, longitude, altitude, precision, globe } = snak.datavalue.value if (_.isPlainObject(searchedValue)) { if (latitude !== searchedValue.latitude) return false if (longitude !== searchedValue.longitude) return false if (precision !== searchedValue.precision) return false if (globe !== searchedValue.globe) return false if (!(altitude === null && searchedValue.altitude === undefined)) { if (altitude !== searchedValue.altitude) return false } return true } else { return latitude === searchedValue[0] && longitude === searchedValue[1] } }, math: simpleValueComparison, 'musical-notation': simpleValueComparison, monolingualtext: (snak, searchedValue) => { const { text, language } = snak.datavalue.value return language === searchedValue.language && text === searchedValue.text }, quantity: (snak, searchedValue) => { let { amount, lowerBound, upperBound } = snak.datavalue.value amount = parseAmount(amount) lowerBound = parseAmount(lowerBound) upperBound = parseAmount(upperBound) if (_.isPlainObject(searchedValue)) { const searchedAmount = parseAmount(searchedValue.amount) const searchedLowerBound = parseAmount(searchedValue.lowerBound) const searchedUpperBound = parseAmount(searchedValue.upperBound) const searchUnit = parseUnit(searchedValue.unit) const snakUnit = getUnit(snak) // Unspecified units do not prevent a match if (searchUnit !== '1' && snakUnit !== '1' && searchUnit !== snakUnit) { return false } if (lowerBound != null && searchedLowerBound != null && lowerBound !== searchedLowerBound) { return false } if (upperBound != null && searchedUpperBound != null && upperBound !== searchedUpperBound) { return false } return searchedAmount === amount } else { return amount === parseAmount(searchedValue) } }, string: simpleValueComparison, 'tabular-data': simpleValueComparison, time: (snak, searchedValue) => { const { time, timezone, before, after, precision, calendarmodel } = snak.datavalue.value if (searchedValue.time != null) { if (searchedValue.timezone != null && searchedValue.timezone !== timezone) return false if (searchedValue.before != null && searchedValue.before !== before) return false if (searchedValue.after != null && searchedValue.after !== after) return false if (searchedValue.precision != null && searchedValue.precision !== precision) return false if (searchedValue.calendarmodel != null && searchedValue.calendarmodel !== calendarmodel) return false const normalizedSnakTime = normalizeTime(time, searchedValue.precision) const normalizedInputValueTime = normalizeTime(searchedValue.time, searchedValue.precision) return normalizedSnakTime === normalizedInputValueTime } else { const simplifiedSnak = simplifySnak(snak, { timeConverter: 'simple-day' }) return simplifiedSnak === searchedValue } }, url: (snak, searchedValue) => normalizeUrl(snak.datavalue.value) === normalizeUrl(searchedValue), 'wikibase-form': entityValueComparison, 'wikibase-item': entityValueComparison, 'wikibase-lexeme': entityValueComparison, 'wikibase-property': entityValueComparison, 'wikibase-sense': entityValueComparison, } const getUnit = snak => parseUnit(snak.datavalue.value.unit) const parseAmount = amount => _.isString(amount) ? parseFloat(amount) : amount const normalizeTime = (time, precison) => { time = time.replace(/^\+/, '') if (precison <= 11) { time = time .replace(/-00/g, '-01') .split('T')[0] } return time } const normalizeUrl = url => url.replace(/\/$/, '').replace(/\/\/www\./, '//').toLowerCase() wikibase-edit-5.3.0/lib/claim/move.js000066400000000000000000000105171433026310300173760ustar00rootroot00000000000000const error_ = require('../error') const { isGuid, isEntityId, isPropertyId, getEntityIdFromGuid } = require('wikibase-sdk') const { getEntityClaims } = require('../get_entity') const { findClaimByGuid } = require('./helpers') const { propertiesDatatypesDontMatch } = require('./move_commons') module.exports = async (params, config, API) => { const { guid, propertyClaimsId, id: targetEntityId, property: targetPropertyId, baserevid } = params let originEntityId, originPropertyId if (guid) { if (!isGuid(guid)) throw error_.new('invalid claim guid', 400, params) originEntityId = getEntityIdFromGuid(guid) } else if (propertyClaimsId) { ([ originEntityId, originPropertyId ] = propertyClaimsId.split('#')) if (!(isEntityId(originEntityId) && isPropertyId(originPropertyId))) { throw error_.new('invalid property claims id', 400, params) } } else { throw error_.new('missing claim guid or property claims id', 400, params) } if (!targetEntityId) throw error_.new('missing target entity id', 400, params) if (!isEntityId(targetEntityId)) throw error_.new('invalid target entity id', 400, params) if (!targetPropertyId) throw error_.new('missing property id', 400, params) const propertyDatatype = config.properties[targetPropertyId] const claims = await getEntityClaims(originEntityId, config) let movedClaims if (guid) { const claim = findClaimByGuid(claims, guid) if (!claim) throw error_.new('claim not found', 400, params) originPropertyId = claim.mainsnak.property movedClaims = [ claim ] } else { movedClaims = claims[originPropertyId] if (!movedClaims) throw error_.new('no property claims found', 400, params) } if (originEntityId === targetEntityId && originPropertyId === targetPropertyId) { throw error_.new("move operation wouldn't have any effect: same entity, same property", 400, params) } const { datatype: currentPropertyDatatype } = movedClaims[0].mainsnak if (propertyDatatype !== currentPropertyDatatype) { propertiesDatatypesDontMatch({ movedSnaks: movedClaims, originPropertyId, originDatatype: currentPropertyDatatype, targetPropertyId, targetDatatype: propertyDatatype, }) } const currentEntityData = { rawMode: true, id: originEntityId, claims: movedClaims.map(claim => ({ id: claim.id, remove: true })), summary: params.summary || config.summary || generateCurrentEntitySummary(guid, originEntityId, originPropertyId, targetEntityId, targetPropertyId) } movedClaims.forEach(claim => { delete claim.id claim.mainsnak.property = targetPropertyId }) if (originEntityId === targetEntityId) { currentEntityData.claims.push(...movedClaims) currentEntityData.baserevid = baserevid const res = await API.entity.edit(currentEntityData, config) return [ res ] } else { if (baserevid) throw error_.new('commands editing multiple entities can not have a baserevid', 400, params) const targetEntityData = { rawMode: true, id: targetEntityId, claims: movedClaims, summary: params.summary || config.summary || generateTargetEntitySummary(guid, originEntityId, originPropertyId, targetEntityId, targetPropertyId) } const removeClaimsRes = await API.entity.edit(currentEntityData, config) const addClaimsRes = await API.entity.edit(targetEntityData, config) return [ removeClaimsRes, addClaimsRes ] } } const generateCurrentEntitySummary = (guid, originEntityId, originPropertyId, targetEntityId, targetPropertyId) => { if (guid) { if (originEntityId === targetEntityId) { return `moving a ${originPropertyId} claim to ${targetPropertyId}` } else { return `moving a ${originPropertyId} claim to ${targetEntityId}#${targetPropertyId}` } } else { if (originEntityId === targetEntityId) { return `moving ${originPropertyId} claims to ${targetPropertyId}` } else { return `moving ${originPropertyId} claims to ${targetEntityId}#${targetPropertyId}` } } } const generateTargetEntitySummary = (guid, originEntityId, originPropertyId, targetEntityId, targetPropertyId) => { if (guid) { return `moving a ${originEntityId}#${originPropertyId} claim from ${targetEntityId}#${targetPropertyId}` } else { return `moving ${originEntityId}#${originPropertyId} claims from ${targetEntityId}#${targetPropertyId}` } } wikibase-edit-5.3.0/lib/claim/move_commons.js000066400000000000000000000035331433026310300211310ustar00rootroot00000000000000const error_ = require('../error') const { simplify } = require('wikibase-sdk') const { parseQuantity } = require('./quantity') const issuesUrl = require('../../package.json').bugs.url const propertiesDatatypesDontMatch = params => { const { movedSnaks, originDatatype, targetDatatype } = params const typeConverterKey = `${originDatatype}->${targetDatatype}` const convertType = snakTypeConversions[typeConverterKey] if (convertType) { for (let snak of movedSnaks) { snak = snak.mainsnak || snak if (snakHasValue(snak)) { try { convertType(snak) } catch (err) { const errMessage = `properties datatype don't match and ${typeConverterKey} type conversion failed: ${err.message}` params.failingSnak = snak throw error_.new(errMessage, 400, params) } } } } else { const errMessage = `properties datatype don't match No ${typeConverterKey} type converter found If you think that should be possible, please open a ticket: ${issuesUrl}/new?template=feature_request.md&title=${encodeURIComponent(`claim.move: add a ${typeConverterKey} type converter`)}&body=%20` throw error_.new(errMessage, 400, params) } } const simplifyToString = snak => { snak.datavalue.value = simplify.snak(snak).toString() snak.datatype = snak.datavalue.type = 'string' } const snakTypeConversions = { 'string->external-id': snak => { snak.datatype = 'string' }, 'string->quantity': snak => { const { value } = snak.datavalue snak.datavalue.value = parseQuantity(value) snak.datatype = snak.datavalue.type = 'quantity' }, 'external-id->string': simplifyToString, 'monolingualtext->string': simplifyToString, 'quantity->string': simplifyToString, } const snakHasValue = snak => snak.snaktype === 'value' module.exports = { propertiesDatatypesDontMatch } wikibase-edit-5.3.0/lib/claim/parse_calendar.js000066400000000000000000000013171433026310300213710ustar00rootroot00000000000000const wdUrlBase = 'http://www.wikidata.org/entity/' const gregorian = `${wdUrlBase}Q1985727` const julian = `${wdUrlBase}Q1985786` const calendarAliases = { julian, gregorian, Q1985727: gregorian, Q1985786: julian } module.exports = (calendar, wikidataTimeString) => { if (!calendar) return getDefaultCalendar(wikidataTimeString) const normalizedCalendar = calendar.replace(wdUrlBase, '') return calendarAliases[normalizedCalendar] } const getDefaultCalendar = wikidataTimeString => { if (wikidataTimeString[0] === '-') return julian const [ year ] = wikidataTimeString .replace('+', '') .split('-') .map(num => parseInt(num)) if (year > 1582) return gregorian else return julian } wikibase-edit-5.3.0/lib/claim/quantity.js000066400000000000000000000023141433026310300203020ustar00rootroot00000000000000const _ = require('../utils') const { isItemId } = require('wikibase-sdk') const error_ = require('../error') const itemUnitPattern = /^http.*\/entity\/(Q\d+)$/ module.exports = { parseQuantity: (amount, instance) => { let unit, upperBound, lowerBound if (_.isPlainObject(amount)) ({ amount, unit, upperBound, lowerBound } = amount) if (isItemId(unit)) unit = `${forceHttp(instance)}/entity/${unit}` validateNumber('amount', amount) validateNumber('upperBound', upperBound) validateNumber('lowerBound', lowerBound) unit = unit || '1' return { amount: signAmount(amount), unit, upperBound, lowerBound } }, parseUnit: unit => { if (unit.match(itemUnitPattern)) unit = unit.replace(itemUnitPattern, '$1') return unit } } const signAmount = amount => { if (_.isSignedStringNumber(amount)) return `${amount}` if (_.isStringNumber(amount)) amount = parseFloat(amount) if (amount === 0) return '0' return amount > 0 ? `+${amount}` : `${amount}` } const forceHttp = instance => instance.replace('https:', 'http:') const validateNumber = (label, num) => { if (_.isString(num) && !_.isStringNumber(num)) { throw error_.new('invalid string number', { [label]: num }) } } wikibase-edit-5.3.0/lib/claim/remove.js000066400000000000000000000022551433026310300177250ustar00rootroot00000000000000const { forceArray } = require('../utils') const validate = require('../validate') const error_ = require('../error') const { getEntityClaims } = require('../get_entity') const isMatchingClaim = require('./is_matching_claim') const buildClaim = require('../entity/build_claim') module.exports = async (params, properties, instance, config) => { let { guid } = params const { id, property, value, qualifiers, reconciliation = {} } = params if (!(guid || (id && property && value))) { throw error_.new('missing guid or id/property/value', params) } if (!guid) { const existingClaims = await getEntityClaims(id, config) const claimData = { value, qualifiers } const claim = buildClaim(property, properties, claimData, instance) const { matchingQualifiers } = reconciliation const matchingClaims = existingClaims[property].filter(isMatchingClaim(claim, matchingQualifiers)) if (matchingClaims.length === 0) throw error_.new('claim not found', params) guid = matchingClaims.map(({ id }) => id) } const guids = forceArray(guid) guids.forEach(validate.guid) return { action: 'wbremoveclaims', data: { claim: guids.join('|') } } } wikibase-edit-5.3.0/lib/claim/set.js000066400000000000000000000013401433026310300172150ustar00rootroot00000000000000const validate = require('../validate') const formatClaimValue = require('./format_claim_value') const { buildSnak } = require('./snak') module.exports = (params, properties, instance) => { const { guid, property, value: rawValue } = params const datatype = properties[property] validate.guid(guid) validate.property(property) // Format before testing validity to avoid throwing on type errors // that could be recovered const value = formatClaimValue(datatype, rawValue, instance) validate.snakValue(property, datatype, value) const claim = { id: guid, type: 'statement', mainsnak: buildSnak(property, datatype, value) } return { action: 'wbsetclaim', data: { claim: JSON.stringify(claim) } } } wikibase-edit-5.3.0/lib/claim/snak.js000066400000000000000000000023631433026310300173640ustar00rootroot00000000000000const _ = require('../utils') const validate = require('../validate') const datatypesToBuilderDatatypes = require('../properties/datatypes_to_builder_datatypes') const { entityEditBuilders: builders } = require('./builders') const buildSnak = (property, datatype, value, instance) => { value = value.value || value if (value && value.snaktype && value.snaktype !== 'value') { return { snaktype: value.snaktype, property } } const builderDatatype = datatypesToBuilderDatatypes(datatype) return builders[builderDatatype](property, value, instance).mainsnak } const buildReference = (properties, instance) => reference => { const hash = reference.hash const referenceSnaks = reference.snaks || reference const snaksPerProperty = _.map(referenceSnaks, buildPropSnaks(properties, instance)) const snaks = _.flatten(_.values(snaksPerProperty)) return { snaks, hash } } const buildPropSnaks = (properties, instance) => (prop, propSnakValues) => { validate.property(prop) return _.forceArray(propSnakValues).map(snakValue => { const datatype = properties[prop] validate.snakValue(prop, datatype, snakValue) return buildSnak(prop, datatype, snakValue, instance) }) } module.exports = { buildSnak, buildReference, buildPropSnaks } wikibase-edit-5.3.0/lib/claim/snak_post_data.js000066400000000000000000000012571433026310300214230ustar00rootroot00000000000000const { singleClaimBuilders: builders } = require('./builders') const { hasSpecialSnaktype } = require('./special_snaktype') const datatypesToBuilderDatatypes = require('../properties/datatypes_to_builder_datatypes') const error_ = require('../error') module.exports = params => { const { action, data, datatype, value, instance } = params if (!datatype) throw error_.new('missing datatype', params) if (hasSpecialSnaktype(value)) { data.snaktype = value.snaktype } else { data.snaktype = 'value' const builderDatatype = datatypesToBuilderDatatypes(datatype) || datatype data.value = builders[builderDatatype](value, instance) } return { action, data } } wikibase-edit-5.3.0/lib/claim/special_snaktype.js000066400000000000000000000005601433026310300217630ustar00rootroot00000000000000const error_ = require('../error') module.exports = { hasSpecialSnaktype: value => { if (typeof value !== 'object') return false const { snaktype } = value if (snaktype == null || snaktype === 'value') return false if (snaktype === 'novalue' || snaktype === 'somevalue') return true else throw error_.new('invalid snaktype', { snaktype }) } } wikibase-edit-5.3.0/lib/claim/update.js000066400000000000000000000040521433026310300177070ustar00rootroot00000000000000const { isGuid, getEntityIdFromGuid } = require('wikibase-sdk') const { getEntityClaims } = require('../get_entity') const error_ = require('../error') const findSnak = require('./find_snak') const { findClaimByGuid, isGuidClaim, simplifyClaimForEdit } = require('./helpers') module.exports = async (params, config, API) => { let { id, guid, property } = params const { oldValue, newValue, rank } = params if (!(rank != null || newValue != null)) { throw error_.new('expected a rank or a newValue', 400, params) } if (isGuid(guid)) { id = getEntityIdFromGuid(guid) } else { const values = { oldValue, newValue } if (oldValue === newValue) { throw error_.new("old and new claim values can't be the same", 400, values) } if (typeof oldValue !== typeof newValue) { throw error_.new('old and new claim should have the same type', 400, values) } } const claims = await getEntityClaims(id, config) let claim if (guid) { claim = findClaimByGuid(claims, guid) property = claim && claim.mainsnak.property } else { claim = findSnak(property, claims[property], oldValue) } if (!claim) { throw error_.new('claim not found', 400, params) } const simplifiedClaim = simplifyClaimForEdit(claim) guid = claim.id if (rank) simplifiedClaim.rank = rank if (newValue) { if (newValue.snaktype && newValue.snaktype !== 'value') { simplifiedClaim.snaktype = newValue.snaktype delete simplifiedClaim.value } else { simplifiedClaim.value = newValue } } const data = { id, claims: { [property]: simplifiedClaim }, // Using wbeditentity, as the endpoint is more complete, so we need to recover the summary summary: params.summary || config.summary || `update ${property} claim`, baserevid: params.baserevid || config.baserevid, } const { entity, success } = await API.entity.edit(data, config) const updatedClaim = entity.claims[property].find(isGuidClaim(guid)) // Mimick claim actions responses return { claim: updatedClaim, success } } wikibase-edit-5.3.0/lib/datatype_tests.js000066400000000000000000000064721433026310300204050ustar00rootroot00000000000000const { isEntityId, isItemId } = require('wikibase-sdk') const _ = require('./utils') const { parseUnit } = require('./claim/quantity') const parseCalendar = require('./claim/parse_calendar') const error_ = require('./error') module.exports = { string: _.isNonEmptyString, entity: isEntityId, // See https://www.mediawiki.org/wiki/Wikibase/DataModel#Dates_and_times // The positive years will be signed later time: time => { time = time.value || time let precision, calendar, calendarmodel if (_.isPlainObject(time)) { const dateObject = time; ({ precision, calendar, calendarmodel, time } = time) if (typeof precision === 'number' && (precision < 0 || precision > 14)) { return false } if (precision > 11) throw error_.new('time precision not supported by the Wikibase API', dateObject) calendarmodel = calendarmodel || calendar if (calendarmodel && !parseCalendar(calendarmodel, time)) { throw error_.new('invalid calendar', dateObject) } } time = time.toString() const sign = time[0] === '-' ? '-' : '+' const year = time.replace(/^(-|\+)/, '').split('-')[0] // Parsing as an ISO String should not throw an Invalid time value error // Only trying to parse 5-digit years or below, as higher years // will fail, even when valid Wikibase dates // Excluding negative years as the format expected by Wikibase // doesn't have the padding zeros expected by ISO if (sign === '+' && year.length <= 5) { try { time = time.replace(/^\+/, '') let isoTime = time // ISO validation would fail if either date or month are 0 // Replace date or date and month digits with 01 // if precision is less than 11 or 10, respectively if (precision != null) { if (precision < 10) { isoTime = isoTime.replace(/^(\d{4})-\d{1,2}-\d{1,2}/, '$1-01-01') } else if (precision < 11) { isoTime = isoTime.replace(/^(\d{4}-\d{1,2})-\d{1,2}/, '$1-01') } } new Date(isoTime).toISOString() } catch (err) { return false } } if (precision != null && precision > 11) { return /^(-|\+)?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2,3}Z$/.test(time) } else if (time.match('T')) { return /^(-|\+)?\d{4,16}-\d{2}-\d{2}T00:00:00(\.000)?Z$/.test(time) } else { return /^(-|\+)?\d{4,16}(-\d{2}){0,2}$/.test(time) } }, monolingualtext: value => { value = value.value || value const { text, language } = value return _.isNonEmptyString(text) && _.isNonEmptyString(language) }, // cf https://www.mediawiki.org/wiki/Wikibase/DataModel#Quantities quantity: amount => { amount = amount.value || amount if (_.isPlainObject(amount)) { let unit ;({ unit, amount } = amount) if (unit && !isItemId(parseUnit(unit)) && unit !== '1') return false } // Accepting both numbers or string numbers as the amount will be // turned as a string lib/claim/builders.js signAmount function anyway return _.isNumber(amount) || _.isStringNumber(amount) }, globecoordinate: obj => { obj = obj.value || obj if (!_.isPlainObject(obj)) return false const { latitude, longitude, precision } = obj return _.isNumber(latitude) && _.isNumber(longitude) && _.isNumber(precision) } } wikibase-edit-5.3.0/lib/description/000077500000000000000000000000001433026310300173245ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/description/set.js000066400000000000000000000001071433026310300204530ustar00rootroot00000000000000module.exports = require('../label_or_description/set')('description') wikibase-edit-5.3.0/lib/entity/000077500000000000000000000000001433026310300163155ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/entity/build_claim.js000066400000000000000000000063111433026310300211200ustar00rootroot00000000000000const _ = require('../utils') const error_ = require('../error') const validate = require('../validate') const { entityEditBuilders: builders } = require('../claim/builders') const { buildReference, buildPropSnaks } = require('../claim/snak') const { hasSpecialSnaktype } = require('../claim/special_snaktype') const datatypesToBuilderDatatypes = require('../properties/datatypes_to_builder_datatypes') module.exports = (property, properties, claimData, instance) => { const datatype = properties[property] const builderDatatype = datatypesToBuilderDatatypes(datatype) const builder = builders[builderDatatype] const params = { properties, datatype, property, claimData, builder, instance } if (_.isString(claimData) || _.isNumber(claimData)) { return simpleClaimBuilder(params) } else { if (!_.isPlainObject(claimData)) throw error_.new('invalid claim data', { property, claimData }) return fullClaimBuilder(params) } } const simpleClaimBuilder = params => { const { property, datatype, claimData: value, builder, instance } = params validate.snakValue(property, datatype, value) return builder(property, value, instance) } const fullClaimBuilder = params => { const { properties, datatype, property, claimData, builder, instance } = params validateClaimParameters(claimData) let { id, value, snaktype, rank, qualifiers, references, remove, reconciliation } = claimData if (remove === true) { if (!(id || reconciliation)) throw error_.new("can't remove a claim without an id or reconciliation settings", claimData) if (id) return { id, remove: true } } let claim if (value && value.snaktype) { claimData.snaktype = snaktype = value.snaktype } if (hasSpecialSnaktype(claimData)) { claim = builders.specialSnaktype(property, snaktype) } else { // In case of a rich value (monolingual text, quantity, globe coordinate, or time) if (value == null && (claimData.text || claimData.amount || claimData.latitude || claimData.time)) { value = claimData } validate.snakValue(property, datatype, value) claim = builder(property, value, instance) } if (id) { validate.guid(id) claim.id = id } if (rank) { validate.rank(rank) claim.rank = rank } if (qualifiers) { claim.qualifiers = _.map(qualifiers, buildPropSnaks(properties, instance)) } if (references) { claim.references = _.forceArray(references).map(buildReference(properties, instance)) } if (reconciliation) claim.reconciliation = reconciliation if (remove) claim.remove = remove return claim } const validClaimParameters = [ 'id', 'type', 'value', 'snaktype', 'rank', 'qualifiers', 'references', 'remove', 'reconciliation', 'text', 'language', 'amount', 'lowerBound', 'upperBound', 'unit', 'latitude', 'longitude', 'precision', 'globe', 'altitude', 'time', 'timezone', 'before', 'after', 'precision', 'calendarmodel', ] const validClaimParametersSet = new Set(validClaimParameters) const validateClaimParameters = claimData => { for (const key in claimData) { if (!validClaimParametersSet.has(key)) { throw error_.new(`invalid claim parameter: ${key}`, { claimData, validClaimParameters }) } } } wikibase-edit-5.3.0/lib/entity/create.js000066400000000000000000000004471433026310300201230ustar00rootroot00000000000000const edit = require('./edit') const error_ = require('../error') module.exports = async (params, properties, instance) => { const { id } = params if (id) throw error_.new("a new entity can't already have an id", { id }) params.create = true return edit(params, properties, instance) } wikibase-edit-5.3.0/lib/entity/delete.js000066400000000000000000000005431433026310300201170ustar00rootroot00000000000000const { isEntityId } = require('wikibase-sdk') const error_ = require('../error') module.exports = params => { const { id } = params if (!isEntityId(id)) throw error_.new('invalid entity id', params) return { action: 'delete', data: { // The title will be prefixified if needed by ./lib/resolve_title.js title: id } } } wikibase-edit-5.3.0/lib/entity/edit.js000066400000000000000000000071061433026310300176040ustar00rootroot00000000000000const { isEntityId } = require('wikibase-sdk') const error_ = require('../error') const format = require('./format') const { getEntityClaims } = require('../get_entity') const { forceArray } = require('../utils') const { isIdAliasPattern, resolveIdAlias } = require('./id_alias') module.exports = async (data, properties, instance, config) => { validateParameters(data) let { id } = data const { create, type, datatype, clear, rawMode, reconciliation } = data const params = { data: {} } let existingClaims if (type && type !== 'property' && type !== 'item') { throw error_.new('invalid entity type', { type }) } if (create) { if (type === 'property') { if (!datatype) throw error_.new('missing property datatype', { datatype }) if (!datatypes.has(datatype)) { throw error_.new('invalid property datatype', { datatype, knownDatatypes: datatypes }) } params.new = 'property' params.data.datatype = datatype } else { if (datatype) { throw error_.new("an item can't have a datatype", { datatype }) } params.new = 'item' } } else if (isEntityId(id) || isIdAliasPattern(id)) { if (isIdAliasPattern(id)) { id = await resolveIdAlias(id, instance) } params.id = id if (hasReconciliationSettings(reconciliation, data.claims)) { existingClaims = await getEntityClaims(id, config) } } else { throw error_.new('invalid entity id', { id }) } const { labels, aliases, descriptions, claims, sitelinks } = data if (rawMode) { if (labels) params.data.labels = labels if (aliases) params.data.aliases = aliases if (descriptions) params.data.descriptions = descriptions if (claims) params.data.claims = claims if (sitelinks) params.data.sitelinks = sitelinks } else { if (labels) params.data.labels = format.values('label', labels) if (aliases) params.data.aliases = format.values('alias', aliases) if (descriptions) params.data.descriptions = format.values('description', descriptions) if (claims) params.data.claims = format.claims(claims, properties, instance, reconciliation, existingClaims) if (sitelinks) params.data.sitelinks = format.sitelinks(sitelinks) } if (clear === true) params.clear = true if (!clear && Object.keys(params.data).length === 0) { throw error_.new('no data was passed', { id }) } // stringify as it will be passed as form data params.data = JSON.stringify(params.data) return { action: 'wbeditentity', data: params } } const datatypes = new Set([ 'commonsMedia', 'external-id', 'geo-shape', 'globe-coordinate', // datatype from https://github.com/ProfessionalWiki/WikibaseLocalMedia 'localMedia', 'math', 'monolingualtext', 'musical-notation', 'quantity', 'string', 'tabular-data', 'time', 'url', 'wikibase-form', 'wikibase-item', 'wikibase-property', 'wikibase-lexeme', ]) const allowedParameters = new Set([ 'id', 'create', 'type', 'datatype', 'clear', 'rawMode', 'summary', 'baserevid', 'labels', 'aliases', 'descriptions', 'claims', 'sitelinks', 'reconciliation', ]) const validateParameters = data => { for (const parameter in data) { if (!allowedParameters.has(parameter)) { throw error_.new(`invalid parameter: ${parameter}`, 400, { parameter, allowedParameters, data }) } } } const hasReconciliationSettings = (reconciliation, claims) => { if (reconciliation != null) return true for (const property in claims) { for (const claim of forceArray(claims[property])) { if (claim.reconciliation != null) return true } } return false } wikibase-edit-5.3.0/lib/entity/format.js000066400000000000000000000067341433026310300201550ustar00rootroot00000000000000const { isString, forceArray, isntEmpty, flatten } = require('../utils') const validate = require('../validate') const buildClaim = require('./build_claim') const error_ = require('../error') const reconcileClaim = require('./reconcile_claim') const formatBadgesArray = badges => { if (isString(badges)) { badges = badges.split('|') } validate.badges(badges) return badges } module.exports = { values: (name, values) => { const obj = {} Object.keys(values).forEach(lang => { let value = values[lang] validate.language(lang) if (name === 'alias') { value = forceArray(value) validate.aliases(value, { allowEmptyArray: true }) obj[lang] = value.map(alias => buildLanguageValue(alias, lang)) } else { validate.labelOrDescription(name, value) obj[lang] = buildLanguageValue(value, lang) } }) return obj }, claims: (claims, properties, instance, reconciliation, existingClaims) => { if (!properties) throw error_.new('expected properties') return Object.keys(claims) .reduce(formatClaim(claims, properties, instance, reconciliation, existingClaims), {}) }, sitelinks: sitelinks => { const obj = {} Object.keys(sitelinks).forEach(site => { validate.site(site) const title = sitelinks[site] if (title === null) { // Passing an empty string removes the sitelink obj[site] = buildSiteTitle('', site) } else { validate.siteTitle(title) obj[site] = buildSiteTitle(title, site) } }) return obj }, badges: formatBadgesArray } const formatClaim = (claims, properties, instance, reconciliation, existingClaims) => (obj, property) => { if (!properties) throw error_.new('expected properties') if (!instance) throw error_.new('expected instance') validate.property(property) const values = forceArray(claims[property]) obj[property] = obj[property] || [] obj[property] = values.map(value => buildClaim(property, properties, value, instance)) if (existingClaims != null && existingClaims[property] != null) { obj[property] = obj[property] .map(reconcileClaim(reconciliation, existingClaims[property])) .filter(isntEmpty) obj[property] = flatten(obj[property]) validateReconciledClaims(obj[property]) } return obj } const buildLanguageValue = (value, language) => { // Re-building an object to avoid passing any undesired key/value const valueObj = { language } if (isString(value)) { valueObj.value = value } else if (value === null) { valueObj.remove = true } else { valueObj.value = value.value || value.title if (value.remove === true) valueObj.remove = true if (value.add != null) valueObj.add = '' } return valueObj } const buildSiteTitle = (title, site) => { // Re-building an object to avoid passing any undesired key/value const valueObj = { site } if (isString(title)) { valueObj.title = title } else { valueObj.title = title.title || title.value if (title.badges) { valueObj.badges = formatBadgesArray(title.badges) } if (title.remove === true) valueObj.remove = true } return valueObj } const validateReconciledClaims = propertyClaims => { const claimsByGuid = {} for (const claim of propertyClaims) { const { id } = claim if (id) { if (claimsByGuid[id] != null) { throw error_.new('can not match several times the same claim', { claim }) } else { claimsByGuid[id] = claim } } } } wikibase-edit-5.3.0/lib/entity/id_alias.js000066400000000000000000000021751433026310300204250ustar00rootroot00000000000000const request = require('../request/request') const error_ = require('../error') const WBK = require('wikibase-sdk') const { isNonEmptyString } = require('../utils') const { isPropertyId } = require('wikibase-sdk/lib/helpers/helpers') const isIdAliasPattern = str => { if (typeof str !== 'string') return false const [ property, id ] = str.split(/[=:]/) return isPropertyId(property) && isNonEmptyString(id) } const resolveIdAlias = async (idAlias, instance) => { const wbk = WBK({ instance }) if (!idAlias.includes('=')) { // Accept both ':' and '=' as separators (as the Wikidata Hub uses one and haswbstatement the other) // but only replace the first instance of ':' to avoid corrupting valid ids containing ':' idAlias = idAlias.replace(':', '=') } const url = wbk.cirrusSearchPages({ haswbstatement: idAlias }) const res = await request('get', { url }) const ids = res.query.search.map(result => result.title) if (ids.length === 1) return ids[0] else throw error_.new('id alias could not be resolved', 400, { idAlias, instance, foundIds: ids }) } module.exports = { isIdAliasPattern, resolveIdAlias, } wikibase-edit-5.3.0/lib/entity/merge.js000066400000000000000000000010311433026310300177450ustar00rootroot00000000000000const { isEntityId, isItemId } = require('wikibase-sdk') const error_ = require('../error') module.exports = params => { const { from, to } = params if (!isEntityId(from)) throw error_.new('invalid "from" entity id', params) if (!isEntityId(to)) throw error_.new('invalid "to" entity id', params) if (!isItemId(from)) throw error_.new('unsupported entity type', params) if (!isItemId(to)) throw error_.new('unsupported entity type', params) return { action: 'wbmergeitems', data: { fromid: from, toid: to } } } wikibase-edit-5.3.0/lib/entity/reconcile_claim.js000066400000000000000000000125051433026310300217660ustar00rootroot00000000000000const error_ = require('../error') const { simplify } = require('wikibase-sdk') const { snak: simplifySnak } = simplify const isEqual = require('lodash.isequal') const isMatchingSnak = require('../claim/is_matching_snak') const validateReconciliationObject = require('./validate_reconciliation_object') const isMatchingClaim = require('../claim/is_matching_claim') module.exports = (reconciliation, existingPropertyClaims) => claim => { reconciliation = claim.reconciliation || reconciliation if (!reconciliation) return claim validateReconciliationObject(reconciliation, claim) const { mode, matchingQualifiers, matchingReferences } = reconciliation if (mode === 'skip-on-any-value') { if (existingPropertyClaims.length > 0) { console.warn(`[wikibase-edit] skipping claim: a claim already exists for that property\n${JSON.stringify({ claim, existingPropertyClaims })}`) return } } const existingClaims = existingPropertyClaims.filter(isMatchingClaim(claim, matchingQualifiers)) if (claim.remove) { if (existingClaims.length > 0) { return existingClaims.map(({ id }) => ({ id, remove: true })) } else { throw error_.new("can't remove claim: claim not found", claim) } } if (existingClaims.length === 0) return claim if (mode === 'skip-on-value-match') { console.warn(`[wikibase-edit] skipping claim: a similar claim already exists\n${JSON.stringify({ claim, existingClaims })}`) } else if (mode === 'merge') { if (existingClaims.length > 1) { throw error_.new('too many matching claims found', { claim, existingClaims }) } const existingClaim = existingClaims[0] if (claim.qualifiers != null) { existingClaim.qualifiers = existingClaim.qualifiers || {} addMissingQualifiers(existingClaim.qualifiers, claim.qualifiers) } if (claim.references != null) { existingClaim.references = existingClaim.references || [] if (matchingReferences) { mergeReferences(existingClaim.references, claim.references, matchingReferences) } else { const currentReference = simplify.references(existingClaim.references) const newReferenceReference = claim.references.filter(isNewReference(currentReference)) existingClaim.references.push(...newReferenceReference) } } return existingClaim } else { throw error_.new('unexpected reconciliation mode', 500, { reconciliation }) } } const addMissingQualifiers = (existingQualifiers, newQualifiers) => { for (const property in newQualifiers) { existingQualifiers[property] = existingQualifiers[property] || [] existingQualifiers[property].push(...newQualifiers[property]) } } const isNewReference = currentReference => reference => { const simplifiedReference = aggregateReferenceSnaks(reference.snaks, true) return !currentReference.some(currentReference => { return isEqual(currentReference, simplifiedReference) }) } const mergeReferences = (existingReferences, newReferences, matchingReferences) => { const addedReferences = [] for (const newReference of newReferences) { const newReferenceSnaks = aggregateReferenceSnaks(newReference.snaks) const matchingReference = existingReferences.find(isMatchingReference(newReferenceSnaks, matchingReferences)) if (matchingReference) { mergeMatchingReference(matchingReference, newReferenceSnaks) } else { addedReferences.push(newReference) } } existingReferences.push(...addedReferences) } const mergeMatchingReference = (matchingReference, newReferenceSnaks) => { for (const property in newReferenceSnaks) { if (property in matchingReference.snaks) { const existingPropertySnaks = matchingReference.snaks[property] const newSnaks = newReferenceSnaks[property] .filter(snak => !hasSomeMatch(existingPropertySnaks, snak)) matchingReference.snaks[property].push(...newSnaks) } else { matchingReference.snaks[property] = newReferenceSnaks[property] } } } const isMatchingReference = (newReferenceSnaks, matchingReferences) => existingReference => { const existingReferenceSnaks = existingReference.snaks for (const property of matchingReferences) { const [ pid, option = 'all' ] = property.split(':') const newPropertySnaks = newReferenceSnaks[pid] const existingPropertySnaks = existingReferenceSnaks[pid] if (newPropertySnaks == null && existingPropertySnaks == null) return false if (newPropertySnaks != null && existingPropertySnaks == null) return false if (newPropertySnaks == null && existingPropertySnaks != null) return false const methodName = methodNameByOption[option] const everyNewSnakHasAnExistingMatch = newPropertySnaks[methodName](snak => { return hasSomeMatch(existingPropertySnaks, snak) }) if (!everyNewSnakHasAnExistingMatch) return false } return true } const methodNameByOption = { any: 'some', all: 'every', } const aggregateReferenceSnaks = (snaksArray, simplify) => { return snaksArray.reduce((aggregatedSnaks, snak) => { const { property } = snak aggregatedSnaks[property] = aggregatedSnaks[property] || [] if (simplify) snak = simplifySnak(snak) aggregatedSnaks[property].push(snak) return aggregatedSnaks }, {}) } const hasSomeMatch = (existingPropertySnaks, snak) => { return existingPropertySnaks.some(existingSnak => isMatchingSnak(existingSnak, snak)) } wikibase-edit-5.3.0/lib/entity/validate_reconciliation_object.js000066400000000000000000000031111433026310300250500ustar00rootroot00000000000000const error_ = require('../error') const { isPropertyId } = require('wikibase-sdk') module.exports = (reconciliation, claim) => { if (typeof reconciliation !== 'object') throw error_.new('reconciliation should be an object', { reconciliation }) for (const key of Object.keys(reconciliation)) { if (!validReconciliationKeys.includes(key)) { throw error_.new('invalid reconciliation object key', { key, reconciliation, validReconciliationKeys }) } } const { mode, matchingQualifiers, matchingReferences } = reconciliation if (!claim.remove && !validReconciliationModes.includes(mode)) { throw error_.new('invalid reconciliation mode', { mode, validReconciliationModes }) } validateMatchingPropertyArray('matchingQualifiers', matchingQualifiers) validateMatchingPropertyArray('matchingReferences', matchingReferences) } const validateMatchingPropertyArray = (name, array) => { if (array) { if (!(array instanceof Array)) { throw error_.new(`invalid ${name} array`, { [name]: array }) } for (const id of array) { const [ pid, option ] = id.split(':') if (!isPropertyId(pid)) { throw error_.new(`invalid ${name} property id`, { property: pid }) } if (option && !validOptions.includes(option)) { throw error_.new(`invalid ${name} property id option: ${option}`, { id, pid, option }) } } } } const validReconciliationKeys = [ 'mode', 'matchingQualifiers', 'matchingReferences' ] const validReconciliationModes = [ 'skip-on-value-match', 'skip-on-any-value', 'merge' ] const validOptions = [ 'all', 'any' ] wikibase-edit-5.3.0/lib/error.js000066400000000000000000000014611433026310300164720ustar00rootroot00000000000000const error_ = module.exports = { new: (message, statusCode, context) => { const err = new Error(message) if (typeof statusCode !== 'number') { if (context == null) { context = statusCode statusCode = 400 } else { throw error_.new('invalid error status code', 500, { message, statusCode, context }) } } err.statusCode = statusCode if (context) { context = convertSetsIntoArrays(context) err.context = context err.stack += `\n[context] ${JSON.stringify(context)}` } return err } } const convertSetsIntoArrays = context => { const convertedContext = {} for (const key in context) { const value = context[key] convertedContext[key] = value instanceof Set ? Array.from(value) : value } return convertedContext } wikibase-edit-5.3.0/lib/get_entity.js000066400000000000000000000012731433026310300175150ustar00rootroot00000000000000const getJson = require('./request/get_json') const WBK = require('./get_instance_wikibase_sdk') const getEntity = async (id, props, config) => { const { instance } = config const url = WBK(instance).getEntities({ ids: id, props }) const headers = { 'user-agent': config.userAgent } const { entities } = await getJson(url, { headers }) return entities[id] } const getEntityClaims = async (id, config) => { const entity = await getEntity(id, 'claims', config) return entity.claims } const getEntitySitelinks = async (id, config) => { const entity = await getEntity(id, 'sitelinks', config) return entity.sitelinks } module.exports = { getEntityClaims, getEntitySitelinks, } wikibase-edit-5.3.0/lib/get_instance_wikibase_sdk.js000066400000000000000000000002501433026310300225160ustar00rootroot00000000000000const WDK = require('wikibase-sdk') const wdks = {} module.exports = instance => { if (!wdks[instance]) wdks[instance] = WDK({ instance }) return wdks[instance] } wikibase-edit-5.3.0/lib/index.js000066400000000000000000000052121433026310300164460ustar00rootroot00000000000000const requestWrapper = require('./request_wrapper') const bundleWrapper = require('./bundle_wrapper') const GetAuthData = require('./request/get_auth_data') const validateAndEnrichConfig = require('./validate_and_enrich_config') const error_ = require('./error') // Primitives: sync or async functions that return an { action, params } object // passed to request.post by requestWrapper const rawRequestBuilders = { label: { set: require('./label/set') }, description: { set: require('./description/set') }, alias: { set: require('./alias/set'), add: require('./alias/add'), remove: require('./alias/remove') }, claim: { set: require('./claim/set'), remove: require('./claim/remove') }, qualifier: { set: require('./qualifier/set'), remove: require('./qualifier/remove') }, reference: { set: require('./reference/set'), remove: require('./reference/remove') }, entity: { create: require('./entity/create'), edit: require('./entity/edit'), merge: require('./entity/merge'), delete: require('./entity/delete') }, sitelink: { set: require('./sitelink/set'), }, badge: {}, } // Bundles: async functions that make use of the primitives to offer more sophisticated behaviors const bundledRequestsBuilders = { claim: { create: require('./claim/create'), update: require('./claim/update'), move: require('./claim/move') }, qualifier: { update: require('./qualifier/update'), move: require('./qualifier/move') }, badge: { add: require('./badge/add'), remove: require('./badge/remove'), }, } module.exports = (generalConfig = {}) => { if (typeof generalConfig !== 'object') { throw error_.new('invalid general config object', { generalConfig, type: typeof generalConfig }) } const API = {} for (const sectionKey in rawRequestBuilders) { API[sectionKey] = {} for (const functionName in rawRequestBuilders[sectionKey]) { const fn = rawRequestBuilders[sectionKey][functionName] API[sectionKey][functionName] = requestWrapper(fn, generalConfig) } } for (const sectionKey in bundledRequestsBuilders) { for (const functionName in bundledRequestsBuilders[sectionKey]) { const fn = bundledRequestsBuilders[sectionKey][functionName] API[sectionKey][functionName] = bundleWrapper(fn, generalConfig, API) } } API.getAuthData = reqConfig => { const config = validateAndEnrichConfig(generalConfig, reqConfig) return GetAuthData(config) } // Legacy aliases API.claim.add = API.claim.create API.qualifier.add = API.qualifier.set API.reference.add = API.reference.set return API } wikibase-edit-5.3.0/lib/issues.js000066400000000000000000000011571433026310300166560ustar00rootroot00000000000000const newIssueUrl = 'https://github.com/maxlath/wikibase-edit/issues/new' const newIssue = ({ template, title = ' ', body = ' ', context }) => { title = encodeURIComponent(title) if (context != null) { body += 'Context:\n```json\n' + JSON.stringify(context, null, 2) + '\n```\n' } body = encodeURIComponent(body) return `Please open an issue at ${newIssueUrl}?template=${template}&title=${title}&body=${body}` } module.exports = { inviteToOpenAFeatureRequest: ({ title, body, context }) => { return newIssue({ template: 'feature_request.md', title, body, context, }) } } wikibase-edit-5.3.0/lib/label/000077500000000000000000000000001433026310300160605ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/label/set.js000066400000000000000000000001011433026310300172010ustar00rootroot00000000000000module.exports = require('../label_or_description/set')('label') wikibase-edit-5.3.0/lib/label_or_description/000077500000000000000000000000001433026310300211635ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/label_or_description/set.js000066400000000000000000000006511433026310300223160ustar00rootroot00000000000000const validate = require('../validate') const error_ = require('../error') module.exports = name => params => { const { id, language } = params let { value } = params const action = `wbset${name}` validate.entity(id) validate.language(language) if (value === undefined) throw error_.new(`missing ${name}`, params) if (value === null) value = '' return { action, data: { id, language, value } } } wikibase-edit-5.3.0/lib/parse_instance.js000066400000000000000000000011511433026310300203330ustar00rootroot00000000000000const error_ = require('./error') module.exports = config => { if (!config) throw error_.new('missing config object') let { instance, wikibaseInstance } = config // Accept config.wikibaseInstance for legacy support instance = instance || wikibaseInstance if (!instance) throw error_.new('missing config parameter: instance', { config }) let { wgScriptPath = 'w' } = config wgScriptPath = wgScriptPath.replace(/^\//, '') config.instance = instance .replace(/\/$/, '') .replace(`/${wgScriptPath}/api.php`, '') config.instanceApiEndpoint = `${config.instance}/${wgScriptPath}/api.php` } wikibase-edit-5.3.0/lib/properties/000077500000000000000000000000001433026310300171755ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/properties/datatypes_to_builder_datatypes.js000066400000000000000000000014101433026310300260130ustar00rootroot00000000000000const buildersByNormalizedDatatypes = { commonsmedia: 'string', externalid: 'string', globecoordinate: 'globecoordinate', geoshape: 'string', // datatype from https://github.com/ProfessionalWiki/WikibaseLocalMedia localmedia: 'string', math: 'string', monolingualtext: 'monolingualtext', musicalnotation: 'string', quantity: 'quantity', string: 'string', tabulardata: 'string', time: 'time', url: 'string', wikibaseform: 'entity', wikibaseitem: 'entity', wikibaselexeme: 'entity', wikibaseproperty: 'entity', wikibasesense: 'entity', } const allDashesPattern = /-/g module.exports = datatype => { const normalizedDatype = datatype.toLowerCase().replace(allDashesPattern, '') return buildersByNormalizedDatatypes[normalizedDatype] } wikibase-edit-5.3.0/lib/properties/fetch_properties_datatypes.js000066400000000000000000000031051433026310300251550ustar00rootroot00000000000000const getJson = require('../request/get_json') const error_ = require('../error') const WBK = require('../get_instance_wikibase_sdk') const { isPropertyId } = require('wikibase-sdk') const propertiesByInstance = {} module.exports = async (config, propertyIds = []) => { let { instance, properties } = config propertyIds.forEach(propertyId => { if (!isPropertyId(propertyId)) throw error_.new('invalid property id', { propertyId }) }) if (!properties) { propertiesByInstance[instance] = propertiesByInstance[instance] || {} config.properties = properties = propertiesByInstance[instance] } const missingPropertyIds = propertyIds.filter(notIn(properties)) if (missingPropertyIds.length === 0) return const urls = WBK(instance).getManyEntities({ ids: missingPropertyIds, props: 'info' }) const headers = { 'user-agent': config.userAgent } const responses = await Promise.all(urls.map(url => getJson(url, { headers }))) const responsesEntities = responses.map(parseResponse) const allEntities = Object.assign(...responsesEntities) missingPropertyIds.forEach(addMissingProperty(allEntities, properties, instance)) } const notIn = object => key => object[key] == null const parseResponse = ({ entities, error }) => { if (error) throw error_.new(error.info, 400, error) return entities } const addMissingProperty = (entities, properties, instance) => propertyId => { const property = entities[propertyId] if (!(property && property.datatype)) throw error_.new('property not found', { propertyId, instance }) properties[propertyId] = property.datatype } wikibase-edit-5.3.0/lib/properties/fetch_used_properties_datatypes.js000066400000000000000000000014261433026310300262010ustar00rootroot00000000000000const fetchPropertiesDatatypes = require('./fetch_properties_datatypes') const { findClaimsProperties, findSnaksProperties } = require('./find_snaks_properties') module.exports = (params, config) => { const propertyIds = findUsedProperties(params) return fetchPropertiesDatatypes(config, propertyIds) } const findUsedProperties = params => { const ids = [] if (!params.rawMode) { if (params.claims) ids.push(...findClaimsProperties(params.claims)) if (params.snaks) ids.push(...findSnaksProperties(params.snaks)) if (params.property) ids.push(params.property) } if (params.newProperty) ids.push(params.newProperty) if (params.oldProperty) ids.push(params.oldProperty) if (params.propertyClaimsId) ids.push(params.propertyClaimsId.split('#')[1]) return ids } wikibase-edit-5.3.0/lib/properties/find_snaks_properties.js000066400000000000000000000020611433026310300241250ustar00rootroot00000000000000const { forceArray, uniq } = require('../utils') const findClaimsProperties = claims => { if (!claims) return [] const claimsProperties = Object.keys(claims) const subSnaksProperties = [] const addSnaksProperties = snaks => { const properties = findSnaksProperties(snaks) subSnaksProperties.push(...properties) } claimsProperties.forEach(addPropertyClaimsSnaksProperties(claims, addSnaksProperties)) return uniq(claimsProperties.concat(subSnaksProperties)) } const addPropertyClaimsSnaksProperties = (claims, addSnaksProperties) => claimProperty => { const propertyClaims = claims[claimProperty] forceArray(propertyClaims).forEach(claim => { const { qualifiers, references } = claim if (qualifiers) addSnaksProperties(qualifiers) if (references) { forceArray(references).forEach(reference => { const snaks = reference.snaks || reference addSnaksProperties(snaks) }) } }) } const findSnaksProperties = snaks => Object.keys(snaks) module.exports = { findClaimsProperties, findSnaksProperties } wikibase-edit-5.3.0/lib/qualifier/000077500000000000000000000000001433026310300167625ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/qualifier/move.js000066400000000000000000000067501433026310300202760ustar00rootroot00000000000000const error_ = require('../error') const { isGuid, isPropertyId, isHash, getEntityIdFromGuid } = require('wikibase-sdk') const { getEntityClaims } = require('../get_entity') const { findClaimByGuid } = require('../claim/helpers') const { propertiesDatatypesDontMatch } = require('../claim/move_commons') module.exports = async (params, config, API) => { const { guid, oldProperty, newProperty, hash } = params if (!guid) throw error_.new('missing claim guid', 400, params) if (!isGuid(guid)) throw error_.new('invalid claim guid', 400, params) if (!oldProperty) throw error_.new('missing old property', 400, params) if (!isPropertyId(oldProperty)) throw error_.new('invalid old property', 400, params) if (!newProperty) throw error_.new('missing new property', 400, params) if (!isPropertyId(newProperty)) throw error_.new('invalid new property', 400, params) if (hash != null && !isHash(hash)) throw error_.new('invalid hash', 400, params) const currentEntityId = getEntityIdFromGuid(guid) const claims = await getEntityClaims(currentEntityId, config) const claim = findClaimByGuid(claims, guid) if (!claim) throw error_.new('claim not found', 400, params) if (!claim.qualifiers[oldProperty]) { params.foundQualifiers = Object.keys(claim.qualifiers) throw error_.new('no qualifiers found for this property', 400, params) } const originDatatype = config.properties[oldProperty] const targetDatatype = config.properties[newProperty] const recoverDatatypesMismatch = movedSnaks => { if (originDatatype !== targetDatatype) { propertiesDatatypesDontMatch({ movedSnaks, originDatatype, originPropertyId: oldProperty, targetDatatype, targetPropertyId: newProperty, }) } } if (hash) { const qualifier = claim.qualifiers[oldProperty].find(findByHash(hash)) if (!qualifier) { params.foundHashes = claim.qualifiers[oldProperty].map(qualifier => qualifier.hash) throw error_.new('qualifier not found', 400, params) } recoverDatatypesMismatch([ qualifier ]) claim.qualifiers[newProperty] = claim.qualifiers[newProperty] || [] claim.qualifiers[newProperty].push(changeProperty(newProperty, qualifier)) claim.qualifiers[oldProperty] = claim.qualifiers[oldProperty].filter(filterOutByHash(hash)) if (claim.qualifiers[oldProperty].length === 0) delete claim.qualifiers[oldProperty] } else { claim.qualifiers[newProperty] = claim.qualifiers[oldProperty] .map(changeProperty.bind(null, newProperty)) recoverDatatypesMismatch(claim.qualifiers[newProperty]) delete claim.qualifiers[oldProperty] } const entityData = { rawMode: true, id: currentEntityId, claims: [ claim ], summary: params.summary || config.summary || generateSummary(guid, oldProperty, newProperty, hash), baserevid: params.baserevid || config.baserevid, } const res = await API.entity.edit(entityData, config) const updatedClaim = findClaimByGuid(res.entity.claims, guid) return { claim: updatedClaim } } const findByHash = hash => qualifier => qualifier.hash === hash const filterOutByHash = hash => qualifier => qualifier.hash !== hash const changeProperty = (newProperty, qualifier) => { qualifier.property = newProperty return qualifier } const generateSummary = (guid, oldProperty, newProperty, hash) => { if (hash) { return `moving a ${oldProperty} qualifier of ${guid} to ${newProperty}` } else { return `moving ${guid} ${oldProperty} qualifiers to ${newProperty}` } } wikibase-edit-5.3.0/lib/qualifier/remove.js000066400000000000000000000006031433026310300206140ustar00rootroot00000000000000const _ = require('../utils') const validate = require('../validate') module.exports = params => { let { guid, hash } = params validate.guid(guid) if (_.isArray(hash)) { hash.forEach(validate.hash) hash = hash.join('|') } else { validate.hash(hash) } return { action: 'wbremovequalifiers', data: { claim: guid, qualifiers: hash } } } wikibase-edit-5.3.0/lib/qualifier/set.js000066400000000000000000000011021433026310300201050ustar00rootroot00000000000000const validate = require('../validate') const snakPostData = require('../claim/snak_post_data') module.exports = (params, properties, instance) => { const { guid, hash, property, value } = params validate.guid(guid) validate.property(property) const datatype = properties[property] validate.snakValue(property, datatype, value) const data = { claim: guid, property } if (hash != null) { validate.hash(hash) data.snakhash = hash } return snakPostData({ action: 'wbsetqualifier', data, datatype, value, instance }) } wikibase-edit-5.3.0/lib/qualifier/update.js000066400000000000000000000035351433026310300206100ustar00rootroot00000000000000const validate = require('../validate') const { getEntityClaims } = require('../get_entity') const _ = require('../utils') const error_ = require('../error') const findSnak = require('../claim/find_snak') const { getEntityIdFromGuid } = require('wikibase-sdk') module.exports = async (params, config, API) => { const { guid, property, oldValue, newValue } = params validate.guid(guid) validate.property(property) const datatype = config.properties[property] validate.snakValue(property, datatype, oldValue) validate.snakValue(property, datatype, newValue) if (oldValue === newValue) { throw error_.new('same value', 400, oldValue, newValue) } // Get current value snak hash const hash = await getSnakHash(guid, property, oldValue, config) return API.qualifier.set({ guid, hash, property, value: newValue, summary: params.summary || config.summary, baserevid: params.baserevid || config.baserevid, }, config) } const getSnakHash = async (guid, property, oldValue, config) => { const entityId = getEntityIdFromGuid(guid) const claims = await getEntityClaims(entityId, config) const claim = findClaim(claims, guid) if (!claim) throw error_.new('claim not found', 400, guid) if (!claim.qualifiers) throw error_.new('claim qualifiers not found', 400, guid) const propSnaks = claim.qualifiers[property] const qualifier = findSnak(property, propSnaks, oldValue) if (!qualifier) { const actualValues = propSnaks ? propSnaks.map(getSnakValue) : null throw error_.new('qualifier not found', 400, { guid, property, expectedValue: oldValue, actualValues }) } return qualifier.hash } const findClaim = (claims, guid) => { claims = _.flatten(_.values(claims)) for (const claim of claims) { if (claim.id === guid) return claim } } const getSnakValue = snak => snak.datavalue && snak.datavalue.value wikibase-edit-5.3.0/lib/reference/000077500000000000000000000000001433026310300167375ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/reference/remove.js000066400000000000000000000006071433026310300205750ustar00rootroot00000000000000const _ = require('../utils') const validate = require('../validate') module.exports = params => { let { guid, hash } = params validate.guid(guid) if (_.isArray(hash)) { hash.forEach(validate.hash) hash = hash.join('|') } else { validate.hash(hash) } return { action: 'wbremovereferences', data: { statement: guid, references: hash } } } wikibase-edit-5.3.0/lib/reference/set.js000066400000000000000000000013511433026310300200700ustar00rootroot00000000000000const validate = require('../validate') const { buildSnak, buildReference } = require('../claim/snak') module.exports = (params, properties) => { const { guid, property, value, hash } = params let { snaks } = params if (snaks) { snaks = buildReference(properties)(snaks).snaks } else { // Legacy interface validate.guid(guid) validate.property(property) const datatype = properties[property] validate.snakValue(property, datatype, value) snaks = {} snaks[property] = [ buildSnak(property, datatype, value) ] } const data = { statement: guid, snaks: JSON.stringify(snaks) } if (hash) { validate.hash(hash) data.reference = hash } return { action: 'wbsetreference', data } } wikibase-edit-5.3.0/lib/request/000077500000000000000000000000001433026310300164715ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/request/check_known_issues.js000066400000000000000000000007711433026310300227200ustar00rootroot00000000000000const knownIssues = { wbmergeitems: { internal_api_error_TypeError: 'https://phabricator.wikimedia.org/T232925' } } module.exports = (url, err) => { if (!url) return const actionMatch = url.match(/action=(\w+)/) if (!actionMatch) return const action = actionMatch[1] if (knownIssues[action] && knownIssues[action][err.name]) { const ticketUrl = knownIssues[action][err.name] console.error(`this is a known issue, please help documenting it at ${ticketUrl}`) throw err } } wikibase-edit-5.3.0/lib/request/fetch.js000066400000000000000000000012401433026310300201150ustar00rootroot00000000000000const fetch = require('cross-fetch') let isNode try { isNode = process.versions.node != null } catch (err) { isNode = false } let agent if (isNode) { // Using a custom agent to set keepAlive=true // https://nodejs.org/api/http.html#http_class_http_agent // https://github.com/bitinn/node-fetch#custom-agent const http = require('http') const https = require('https') const httpAgent = new http.Agent({ keepAlive: true }) const httpsAgent = new https.Agent({ keepAlive: true }) agent = ({ protocol }) => protocol === 'http:' ? httpAgent : httpsAgent } module.exports = (url, options = {}) => { options.agent = agent return fetch(url, options) } wikibase-edit-5.3.0/lib/request/get_auth_data.js000066400000000000000000000011711433026310300216200ustar00rootroot00000000000000const GetToken = require('./get_token') const error_ = require('../error') module.exports = config => { const getToken = GetToken(config) let tokenPromise let lastTokenRefresh = 0 const refreshToken = refresh => { const now = Date.now() if (!refresh && now - lastTokenRefresh < 30 * 1000) { throw error_.new("last token refreshed less than 30 seconds ago: won't retry", { config }) } lastTokenRefresh = now tokenPromise = getToken() return tokenPromise } return params => { if (params && params.refresh) return refreshToken(true) else return tokenPromise || refreshToken() } } wikibase-edit-5.3.0/lib/request/get_final_token.js000066400000000000000000000037221433026310300221630ustar00rootroot00000000000000const getJson = require('./get_json') const { getSignatureHeaders } = require('./oauth') const contentType = 'application/x-www-form-urlencoded' const error_ = require('../error') module.exports = config => async loginCookies => { const { instanceApiEndpoint, credentials, userAgent } = config const { oauth: oauthTokens } = credentials const url = `${instanceApiEndpoint}?action=query&meta=tokens&type=csrf&format=json` const params = { headers: { 'user-agent': userAgent, 'content-type': contentType } } if (oauthTokens) { const signatureHeaders = getSignatureHeaders({ url, method: 'GET', oauthTokens }) Object.assign(params.headers, signatureHeaders) } else { params.headers.cookie = loginCookies } const body = await getJson(url, params) return parseTokens(loginCookies, instanceApiEndpoint, body) } const parseTokens = async (loginCookies, instanceApiEndpoint, body) => { const { error, query } = body if (error) throw formatError(error, body, instanceApiEndpoint) const { csrftoken } = query.tokens if (csrftoken.length < 40) { throw error_.new('invalid csrf token', { loginCookies, body }) } return { token: csrftoken, cookie: loginCookies } } const formatError = (error, body, instanceApiEndpoint) => { const err = error_.new(`${instanceApiEndpoint} error response: ${error.info}`, { body }) Object.assign(err, error) if (error.code === 'mwoauth-invalid-authorization' && error['*'] != null) { const domainMatch = error['*'].match(/(https?:\/\/.*\/w\/api.php)/) if (domainMatch != null && domainMatch[1] !== instanceApiEndpoint) { const domain = domainMatch[1] err.message += `\n\n***This might be caused by non-matching domains*** between the server domain:\t${domain} and the domain in config:\t${instanceApiEndpoint}\n` err.context.domain = domain err.context.instanceApiEndpoint = instanceApiEndpoint } } return err } wikibase-edit-5.3.0/lib/request/get_json.js000066400000000000000000000004441433026310300206410ustar00rootroot00000000000000const request = require('./request') module.exports = (url, params = {}) => { // Ignore cases were a map function passed an index as second argument // ex: Promise.all(urls.map(getJson)) if (typeof params !== 'object') params = {} params.url = url return request('get', params) } wikibase-edit-5.3.0/lib/request/get_token.js000066400000000000000000000004161433026310300210070ustar00rootroot00000000000000const login = require('./login') const GetFinalToken = require('./get_final_token') module.exports = config => { const getFinalToken = GetFinalToken(config) if (config.credentials.oauth) return getFinalToken else return () => login(config).then(getFinalToken) } wikibase-edit-5.3.0/lib/request/initialize_config_auth.js000066400000000000000000000014041433026310300235350ustar00rootroot00000000000000const GetAuthData = require('./get_auth_data') module.exports = config => { if (!config) throw new Error('missing config') if (config.anonymous) return const credentialsKey = getCredentialsKey(config) const { credentials } = config // Generate the function only once per credentials if (credentials._getAuthData && credentialsKey === credentials._credentialsKey) return credentials._getAuthData = GetAuthData(config) credentials._credentialsKey = credentialsKey } const getCredentialsKey = config => { const { instance } = config const { oauth, username } = config.credentials // Namespacing keys as a oauth.consumer_key could theoretically be a username return username ? `${instance}|u|${username}` : `${instance}|o|${oauth.consumer_key}` } wikibase-edit-5.3.0/lib/request/login.js000066400000000000000000000040361433026310300201420ustar00rootroot00000000000000const fetch = require('./fetch') const qs = require('querystring') const contentType = 'application/x-www-form-urlencoded' const error_ = require('../error') const parseSessionCookies = require('./parse_session_cookies') const parseResponseBody = require('./parse_response_body') module.exports = config => { const headers = { 'user-agent': config.userAgent, 'content-type': contentType } const loginUrl = `${config.instanceApiEndpoint}?action=login&format=json` return login(loginUrl, config, headers) } const login = (loginUrl, config, headers) => { return getLoginToken(loginUrl, config, headers) .then(getSessionCookies(loginUrl, config, headers)) } const getLoginToken = (loginUrl, config, headers) => { const { username: lgname, password: lgpassword } = config.credentials const body = qs.stringify({ lgname, lgpassword }) return fetch(loginUrl, { method: 'post', headers, body }) .then(parseLoginCookies) } const getSessionCookies = (loginUrl, config, headers) => async ({ cookies, token: lgtoken }) => { const { username: lgname, password: lgpassword } = config.credentials const body = qs.stringify({ lgname, lgpassword, lgtoken }) const headersWithCookies = Object.assign({}, headers, { Cookie: cookies }) const res = await fetch(loginUrl, { method: 'post', headers: headersWithCookies, body }) const resBody = await parseResponseBody(res) if (resBody.login.result !== 'Success') { throw error_.new('failed to login: invalid username/password') } const resCookies = res.headers.get('set-cookie') if (!resCookies) { throw error_.new('login error', res.statusCode, { body: resBody }) } if (!sessionCookiePattern.test(resCookies)) { throw error_.new('invalid login cookies', { cookies: resCookies }) } return parseSessionCookies(resCookies) } const sessionCookiePattern = /[sS]ession=\w{32}/ const parseLoginCookies = async res => { const body = await res.json() const token = body.login.token const cookies = res.headers.get('set-cookie') return { token, cookies } } wikibase-edit-5.3.0/lib/request/oauth.js000066400000000000000000000014521433026310300201510ustar00rootroot00000000000000const OAuth = require('oauth-1.0a') const base64 = require('crypto-js/enc-base64') const hmacSHA1 = require('crypto-js/hmac-sha1') const hashFunction = (baseString, key) => base64.stringify(hmacSHA1(baseString, key)) const getOAuthData = ({ consumer_key: key, consumer_secret: secret }) => { return OAuth({ consumer: { key, secret }, signature_method: 'HMAC-SHA1', hash_function: hashFunction }) } module.exports = { getSignatureHeaders: ({ url, method, data, oauthTokens }) => { const { token: key, token_secret: secret } = oauthTokens // Do not extract { authorize, toHeaders } functions as they need their context const oauth = getOAuthData(oauthTokens) const signature = oauth.authorize({ url, method, data }, { key, secret }) return oauth.toHeader(signature) } } wikibase-edit-5.3.0/lib/request/parse_response_body.js000066400000000000000000000015701433026310300230770ustar00rootroot00000000000000module.exports = async res => { const raw = await res.text() let data try { data = JSON.parse(raw) } catch (err) { const customErr = new Error('Could not parse response: ' + raw) customErr.name = 'wrong response format' throw customErr } if (data.error != null) throw requestError(res, data) else return data } const requestError = (res, body) => { const { code, info } = body.error || {} const errMessage = `${code}: ${info}` const err = new Error(errMessage) err.name = code err.statusCode = res.status err.statusMessage = res.statusMessage err.headers = res.headers err.body = body err.url = res.url if (res.url) err.stack += `\nurl: ${res.url}` if (res.status) err.stack += `\nresponse status: ${res.status}` if (body) err.stack += `\nresponse body: ${JSON.stringify(body)}` err.context = { url: res.url, body } return err } wikibase-edit-5.3.0/lib/request/parse_session_cookies.js000066400000000000000000000020431433026310300234170ustar00rootroot00000000000000// - Formatting cookies to match servers expecations (dropping 'HttpOnly') // - Removing all the non-required cookies (reducing noise for debugging) // The final cookie should look like: // docker-wikibase: 'wikibase_session=dqnrrb70jaci8cgdl2s0am0oqtr02cp1; wikibaseUserID=1' // wikimedia servers: 'testwikidatawikiSession=cd7h39ik5lf4leh1dugts9vkta4t2e0d; testwikidatawikiUserID=1; testwikidatawikiUserName=Someusername; centralauth_Token=f355932ed4da146b29f7887179af746b; centralauth_Session=950509520bb4bd3dbc7d595c4b06141c;' module.exports = resCookies => { return resCookies .split(parameterSeparator) .map(formatCookieParameter) .filter(parameter => isRequiredCookieKey(parameter.split('=')[0])) .join(parameterSeparator) .trim() } const parameterSeparator = '; ' const isRequiredCookieKey = key => key.match(/(User\w*|[sS]ession|[tT]oken)$/) const formatCookieParameter = parameter => { return parameter // Somehow, it fails to get cookies if the answer parameter is prefixed with HttpOnly .replace('HttpOnly,', '') .trim() } wikibase-edit-5.3.0/lib/request/post.js000066400000000000000000000040771433026310300200240ustar00rootroot00000000000000const { buildUrl } = require('../utils') const request = require('./request') const throwErrorRes = require('./throw_error_res') module.exports = (action, data, config) => { const { anonymous } = config let tryIt, getAuthData if (anonymous) { tryIt = actionPost(action, data, config) } else { getAuthData = config.credentials._getAuthData tryIt = () => getAuthData().then(actionPost(action, data, config)) } return tryIt() .catch(err => { // Known case of required retrial: token expired if (!anonymous && mayBeSolvedByTokenRefresh(err)) { getAuthData({ refresh: true }) } // And retry once return tryIt() }) } const actionPost = (action, data, config) => authData => { const { instanceApiEndpoint, userAgent, bot, summary, baserevid, tags, maxlag, anonymous, autoRetry } = config const query = { action, format: 'json' } if (bot) { query.bot = true data.assert = 'bot' } else if (!anonymous) { data.assert = 'user' } const params = { url: buildUrl(instanceApiEndpoint, query), headers: { 'User-Agent': userAgent }, autoRetry } if (anonymous) { // The edit token for logged-out users is a hardcoded string of +\ // cf https://phabricator.wikimedia.org/T40417 data.token = '+\\' } else { params.oauth = config.credentials.oauth params.headers.cookie = authData.cookie data.token = authData.token } if (summary != null) data.summary = data.summary || summary if (baserevid != null) data.baserevid = data.baserevid || baserevid if (tags != null) data.tags = data.tags || tags.join('|') if (maxlag !== null) data.maxlag = maxlag || 5 params.body = data return request('post', params) .then(throwErrorRes(`action=${action} error`, params)) } const mayBeSolvedByTokenRefresh = err => { if (!(err && err.body && err.body.error)) return false const errorCode = err.body.error.code || '' return tokenErrors.includes(errorCode) } // Errors that might be resolved by a refreshed token const tokenErrors = [ 'badtoken', 'notoken', 'assertuserfailed' ] wikibase-edit-5.3.0/lib/request/request.js000066400000000000000000000047411433026310300205250ustar00rootroot00000000000000const fetch = require('./fetch') const { wait } = require('../utils') const querystring = require('querystring') const { getSignatureHeaders } = require('./oauth') const checkKnownIssues = require('./check_known_issues') const parseResponseBody = require('./parse_response_body') const timeout = 30000 module.exports = (verb, params) => { const method = verb || 'get' const { url, oauth: oauthTokens, headers, autoRetry = true } = params const { body } = params const maxlag = body && body.maxlag let attempts = 1 let bodyStr if (method === 'post' && body != null) { bodyStr = querystring.stringify(body) headers['Content-Type'] = 'application/x-www-form-urlencoded' } const tryRequest = async () => { if (oauthTokens) { const signatureHeaders = getSignatureHeaders({ url, method, data: body, oauthTokens }) Object.assign(headers, signatureHeaders) } return fetch(url, { method, body: bodyStr, headers, timeout }) .then(parseResponseBody) .catch(err => { checkKnownIssues(url, err) if (autoRetry === false) throw err if (errorIsWorthARetry(err)) { const delaySeconds = getRetryDelay(err.headers) * attempts retryWarn(verb, url, err, delaySeconds, attempts++, maxlag) return wait(delaySeconds * 1000).then(tryRequest) } else { throw err } }) } return tryRequest() } const errorIsWorthARetry = err => { if (errorsWorthARetry.has(err.name) || errorsWorthARetry.has(err.type)) return true // failed-save might be a recoverable error from the server // See https://github.com/maxlath/wikibase-cli/issues/150 if (err.name === 'failed-save') { const { messages } = err.body.error return !messages.some(isValidationErrorMessage) } return false } const isValidationErrorMessage = message => message.name.startsWith('wikibase-validator') const errorsWorthARetry = new Set([ 'maxlag', 'TimeoutError', 'request-timeout', 'wrong response format', ]) const defaultRetryDelay = 5 const getRetryDelay = headers => { const retryAfterSeconds = headers && headers['retry-after'] if (/^\d+$/.test(retryAfterSeconds)) return parseInt(retryAfterSeconds) else return defaultRetryDelay } const retryWarn = (verb, url, err, delaySeconds, attempts, maxlag) => { verb = verb.toUpperCase() console.warn(`[wikibase-edit][WARNING] ${verb} ${url} ${err.message} retrying in ${delaySeconds}s (attempt: ${attempts}, maxlag: ${maxlag}s)`) } wikibase-edit-5.3.0/lib/request/throw_error_res.js000066400000000000000000000004131433026310300222520ustar00rootroot00000000000000const error_ = require('../error') module.exports = (label, params) => body => { if (body.error) { const errMessage = label + ': ' + body.error.info const err = error_.new(errMessage, { params, body }) err.body = body throw err } return body } wikibase-edit-5.3.0/lib/request_wrapper.js000066400000000000000000000023731433026310300205740ustar00rootroot00000000000000const error_ = require('./error') const _ = require('./utils') const fetchUsedPropertiesDatatypes = require('./properties/fetch_used_properties_datatypes') const resolveTitle = require('./resolve_title') const validateAndEnrichConfig = require('./validate_and_enrich_config') const validateParameters = require('./validate_parameters') const post = require('./request/post') const initializeConfigAuth = require('./request/initialize_config_auth') module.exports = (fn, generalConfig) => async (params, reqConfig) => { const config = validateAndEnrichConfig(generalConfig, reqConfig) validateParameters(params) initializeConfigAuth(config) await fetchUsedPropertiesDatatypes(params, config) if (!config.properties) throw error_.new('properties not found', config) const { action, data } = await fn(params, config.properties, config.instance, config) const summary = params.summary || config.summary const baserevid = params.baserevid || config.baserevid if (_.isNonEmptyString(summary)) data.summary = summary if (baserevid != null) data.baserevid = baserevid if (!data.title) return post(action, data, config) const title = await resolveTitle(data.title, config.instanceApiEndpoint) data.title = title return post(action, data, config) } wikibase-edit-5.3.0/lib/resolve_title.js000066400000000000000000000025441433026310300202240ustar00rootroot00000000000000// A module that turns entity ids into full mediawiki page titles, by checking // the Wikibase custom namespace configuration // ex: P1 => Property:P1, Q1 => Q1 OR Item:Q1 const { isEntityId } = require('wikibase-sdk') const getJson = require('./request/get_json') let prefixesMapPromise module.exports = async (title, instanceApiEndpoint) => { if (!isEntityId(title)) return prefixesMapPromise = prefixesMapPromise || getPrefixesMap(instanceApiEndpoint) const prefixesMap = await prefixesMapPromise const idFirstLetter = title[0] const prefix = prefixesMap[idFirstLetter] return prefix === '' ? title : `${prefix}:${title}` } const getPrefixesMap = instanceApiEndpoint => { const infoUrl = `${instanceApiEndpoint}?action=query&meta=siteinfo&siprop=namespaces&format=json` return getJson(infoUrl) .then(parsePrefixesMap) } const parsePrefixesMap = res => { return Object.values(res.query.namespaces) .filter(namespace => namespace.defaultcontentmodel) .filter(namespace => namespace.defaultcontentmodel.startsWith('wikibase')) .reduce(aggregatePrefixes, {}) } const aggregatePrefixes = (prefixesMap, namespace) => { const { defaultcontentmodel, '*': prefix } = namespace const type = defaultcontentmodel.split('-')[1] const firstLetter = type === 'item' ? 'Q' : type[0].toUpperCase() prefixesMap[firstLetter] = prefix return prefixesMap } wikibase-edit-5.3.0/lib/sitelink/000077500000000000000000000000001433026310300166235ustar00rootroot00000000000000wikibase-edit-5.3.0/lib/sitelink/set.js000066400000000000000000000011571433026310300177600ustar00rootroot00000000000000// Doc https://www.wikidata.org/w/api.php?action=help&modules=wbsetsitelink const validate = require('../validate') const format = require('../entity/format') module.exports = ({ id, site, title, badges }) => { validate.entity(id) validate.site(site) validate.siteTitle(title) const params = { action: 'wbsetsitelink', data: { id, linksite: site, linktitle: title, } } // Allow to pass null to delete a sitelink if (title === null) { delete params.data.linktitle } if (badges != null) { params.data.badges = format.badges(badges).join('|') } return params } wikibase-edit-5.3.0/lib/utils.js000066400000000000000000000026231433026310300165020ustar00rootroot00000000000000const qs = require('querystring') const stringNumberPattern = /^(-|\+)?\d+(\.\d+)?$/ const signedStringNumberPattern = /^(-|\+)\d+(\.\d+)?$/ module.exports = { isNonEmptyString: str => typeof str === 'string' && str.length > 0, buildUrl: (base, query) => { query = qs.stringify(query) return `${base}?${query}` }, // helpers to simplify polymorphisms forceArray: obj => { if (obj == null) return [] if (!(obj instanceof Array)) return [ obj ] return obj }, isString: str => typeof str === 'string', isNumber: num => typeof num === 'number', isStringNumber: str => stringNumberPattern.test(str), isSignedStringNumber: str => signedStringNumberPattern.test(str), isArray: array => array instanceof Array, isPlainObject: obj => { if (obj instanceof Array) return false if (obj === null) return false return typeof obj === 'object' }, isntEmpty: value => value != null, flatten: arrays => [].concat.apply([], arrays), map: (obj, fn) => { const aggregator = (index, key) => { index[key] = fn(key, obj[key]) return index } return Object.keys(obj).reduce(aggregator, {}) }, values: obj => Object.keys(obj).map(key => obj[key]), uniq: array => Array.from(new Set(array)), difference: (values, excluded) => { return values.filter(value => !excluded.includes(value)) }, wait: ms => new Promise(resolve => setTimeout(resolve, ms)) } wikibase-edit-5.3.0/lib/validate.js000066400000000000000000000076101433026310300171340ustar00rootroot00000000000000const { isEntityId, isPropertyId, isGuid } = require('wikibase-sdk') const error_ = require('./error') const datatypeTests = require('./datatype_tests') const { hasSpecialSnaktype } = require('./claim/special_snaktype') const { inviteToOpenAFeatureRequest } = require('./issues') const datatypesToBuilderDatatypes = require('./properties/datatypes_to_builder_datatypes') const { isPlainObject, isNonEmptyString, forceArray, isArray } = require('./utils') const { isItemId } = require('wikibase-sdk/lib/helpers/helpers') // For a list of valid languages // see https://www.wikidata.org/w/api.php?action=help&modules=wbgetentities const langRegex = /^\w{2,3}(-[\w-]{2,10})?$/ const siteRegex = /^[a-z_]{2,20}$/ const possibleRanks = [ 'normal', 'preferred', 'deprecated' ] const validateStringValue = (name, str) => { if (str === null) return if (isPlainObject(str)) { if (str.remove === true) return // Required by entity/edit.js validation: // values can be passed as objects to allow for flags (ex: 'remove=true') if (str.value != null) str = str.value // title is the API key for sitelinks if (str.title != null) str = str.title } if (!(isNonEmptyString(str))) { throw error_.new(`invalid ${name}`, { str }) } } module.exports = { entity: entity => { if (!isEntityId(entity)) { throw error_.new('invalid entity', { entity }) } }, property: property => { if (!isNonEmptyString(property)) { throw error_.new('missing property', { property }) } if (!isPropertyId(property)) { throw error_.new('invalid property', { property }) } }, language: language => { if (!(isNonEmptyString(language) && langRegex.test(language))) { throw error_.new('invalid language', { language }) } }, labelOrDescription: validateStringValue, aliases: (value, options = {}) => { const { allowEmptyArray = false } = options value = forceArray(value) if (!allowEmptyArray && value.length === 0) throw error_.new('empty alias array', { value }) // Yes, it's not an label or a description, but it works the same value.forEach(validateStringValue.bind(null, 'alias')) }, snakValue: (property, datatype, value) => { if (hasSpecialSnaktype(value)) return if (value == null) { throw error_.new('missing snak value', { property, value }) } if (value.value) value = value.value const builderDatatype = datatypesToBuilderDatatypes(datatype) if (datatypeTests[builderDatatype] == null) { const context = { property, value, datatype, builderDatatype } const featureRequestMessage = inviteToOpenAFeatureRequest({ title: `add support for ${datatype} datatype`, context }) throw error_.new(`unsupported datatype: ${datatype}\n${featureRequestMessage}`, context) } if (!datatypeTests[builderDatatype](value)) { throw error_.new(`invalid ${builderDatatype} value`, { property, value }) } }, site: site => { if (!(isNonEmptyString(site) && siteRegex.test(site))) { throw error_.new('invalid site', { site }) } }, siteTitle: validateStringValue.bind(null, 'title'), badges: badges => { if (!isArray(badges)) { throw error_.new('invalid badges', { badges }) } for (const badge of badges) { if (!isItemId(badge)) { throw error_.new('invalid badge', { invalidBadge: badge, badges }) } } }, guid: guid => { if (!isNonEmptyString(guid)) { throw error_.new('missing guid', { guid }) } if (!isGuid(guid)) { throw error_.new('invalid guid', { guid }) } }, hash: hash => { // Couldn't find the hash length range // but it looks to be somewhere around 40 characters if (!/^[0-9a-f]{20,80}$/.test(hash)) { throw error_.new('invalid hash', { hash }) } }, rank: rank => { if (possibleRanks.indexOf(rank) === -1) { throw error_.new('invalid rank', { rank }) } } } wikibase-edit-5.3.0/lib/validate_and_enrich_config.js000066400000000000000000000040261433026310300226310ustar00rootroot00000000000000const error_ = require('./error') const parseInstance = require('./parse_instance') const { name, version, homepage } = require('../package.json') const { forceArray } = require('./utils') module.exports = (generalConfig, requestConfig) => { generalConfig.userAgent = generalConfig.userAgent || `${name}/v${version} (${homepage})` let config if (requestConfig) { config = Object.assign({}, generalConfig, requestConfig) } else { config = generalConfig if (config._validatedAndEnriched) return config } parseInstance(config) if (config.instance == null) throw error_.new('invalid config object', { config }) config.anonymous = config.anonymous === true if (!config.credentials && !config.anonymous) throw error_.new('missing credentials', { config }) if (config.credentials) { if (!config.credentials.oauth && (!config.credentials.username || !config.credentials.password)) { throw error_.new('missing credentials') } // Oauth config will be validated by wikibase-token if (config.credentials.oauth && (config.credentials.username || config.credentials.password)) { throw error_.new('credentials can not be both oauth tokens, and a username and password') } // Making sure that the 'bot' flag was explicitly set to true config.bot = config.bot === true } let { summary, tags, baserevid } = config if (summary != null) checkType('summary', summary, 'string') // on wikidata.org: set tag to wikibase-edit by default if (config.instance === 'https://www.wikidata.org') { tags = tags || 'WikibaseJS-edit' } if (tags != null) { tags = forceArray(tags) tags.forEach(tag => checkType('tags', tag, 'string')) config.tags = tags } if (baserevid != null) checkType('baserevid', baserevid, 'number') config._validatedAndEnriched = true return config } const checkType = (name, value, type) => { if (typeof value !== type) { // eslint-disable-line valid-typeof throw error_.new(`invalid config ${name}`, { [name]: value, type: typeof summary }) } } wikibase-edit-5.3.0/lib/validate_parameters.js000066400000000000000000000006201433026310300213510ustar00rootroot00000000000000const error_ = require('./error') module.exports = params => { if (params == null) { const err = error_.new('missing parameters object', { params }) // Expected by wikibase-cli err.code = 'EMPTY_PARAMS' throw err } if (!(params.id || params.guid || params.hash || params.labels || (params.from && params.to))) { throw error_.new('invalid params object', { params }) } } wikibase-edit-5.3.0/package-lock.json000066400000000000000000005426101433026310300174570ustar00rootroot00000000000000{ "name": "wikibase-edit", "version": "5.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wikibase-edit", "version": "5.3.0", "license": "MIT", "dependencies": { "cross-fetch": "^3.0.4", "crypto-js": "^3.1.9-1", "lodash.isequal": "^4.5.0", "oauth-1.0a": "^2.2.6", "wikibase-sdk": "^8.0.0" }, "devDependencies": { "config": "^1.31.0", "eslint": "^7.10.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prefer-arrow": "^1.2.2", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", "git-hooks": "^1.1.10", "mocha": "^9.1.3", "module-alias": "^2.2.2", "nock": "^12.0.3", "should": "^13.2.3" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "dependencies": { "@babel/highlight": "^7.10.4" } }, "node_modules/@babel/helper-validator-identifier": { "version": "7.15.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.15.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { "color-convert": "^1.9.0" }, "engines": { "node": ">=4" } }, "node_modules/@babel/highlight/node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" }, "engines": { "node": ">=4" } }, "node_modules/@babel/highlight/node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "dependencies": { "color-name": "1.1.3" } }, "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", "debug": "^4.1.1", "minimatch": "^3.0.4" }, "engines": { "node": ">=10.10.0" } }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "node_modules/@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, "bin": { "acorn": "bin/acorn" }, "engines": { "node": ">=0.4.0" } }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { "color-convert": "^2.0.1" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" }, "engines": { "node": ">= 8" } }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } }, "node_modules/array-includes": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0", "is-string": "^1.0.5" }, "engines": { "node": ">= 0.4" } }, "node_modules/array.prototype.flat": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "dependencies": { "fill-range": "^7.0.1" }, "engines": { "node": ">=8" } }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/camelcase": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "engines": { "node": ">=10" } }, "node_modules/chalk/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/chalk/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "funding": [ { "type": "individual", "url": "https://paulmillr.com/funding/" } ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "engines": { "node": ">= 8.10.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { "color-name": "~1.1.4" }, "engines": { "node": ">=7.0.0" } }, "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "node_modules/config": { "version": "1.31.0", "resolved": "https://registry.npmjs.org/config/-/config-1.31.0.tgz", "integrity": "sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA==", "dev": true, "dependencies": { "json5": "^1.0.1" }, "engines": { "node": ">= 4.0.0" } }, "node_modules/contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", "dependencies": { "node-fetch": "2.6.7" } }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" }, "engines": { "node": ">= 8" } }, "node_modules/crypto-js": { "version": "3.1.9-1", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" }, "engines": { "node": ">=6.0" }, "peerDependenciesMeta": { "supports-color": { "optional": true } } }, "node_modules/debug/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "node_modules/decamelize": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "dependencies": { "object-keys": "^1.0.12" }, "engines": { "node": ">= 0.4" } }, "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true, "engines": { "node": ">=0.3.1" } }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { "esutils": "^2.0.2" }, "engines": { "node": ">=6.0.0" } }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "dependencies": { "ansi-colors": "^4.1.1" }, "engines": { "node": ">=8.6" } }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/es-abstract": { "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", "dev": true, "dependencies": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", "is-regex": "^1.1.1", "object-inspect": "^1.8.0", "object-keys": "^1.1.1", "object.assign": "^4.1.1", "string.prototype.trimend": "^1.0.1", "string.prototype.trimstart": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", "is-symbol": "^1.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true, "engines": { "node": ">=0.8.0" } }, "node_modules/eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", "@humanwhocodes/config-array": "^0.5.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", "espree": "^7.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.1.2", "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", "progress": "^2.0.0", "regexpp": "^3.1.0", "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { "node": "^10.12.0 || >=12.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-config-standard": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz", "integrity": "sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg==", "dev": true }, "node_modules/eslint-import-resolver-node": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, "dependencies": { "debug": "^2.6.9", "resolve": "^1.13.1" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "dependencies": { "ms": "2.0.0" } }, "node_modules/eslint-module-utils": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "dev": true, "dependencies": { "debug": "^2.6.9", "pkg-dir": "^2.0.0" }, "engines": { "node": ">=4" } }, "node_modules/eslint-module-utils/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "dependencies": { "ms": "2.0.0" } }, "node_modules/eslint-plugin-es": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "dependencies": { "eslint-utils": "^2.0.0", "regexpp": "^3.0.0" }, "engines": { "node": ">=8.10.0" } }, "node_modules/eslint-plugin-import": { "version": "2.22.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", "dev": true, "dependencies": { "array-includes": "^3.1.1", "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", "eslint-import-resolver-node": "^0.3.4", "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", "resolve": "^1.17.0", "tsconfig-paths": "^3.9.0" }, "engines": { "node": ">=4" } }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "dependencies": { "ms": "2.0.0" } }, "node_modules/eslint-plugin-import/node_modules/doctrine": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "dependencies": { "esutils": "^2.0.2", "isarray": "^1.0.0" }, "engines": { "node": ">=0.10.0" } }, "node_modules/eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "dependencies": { "eslint-plugin-es": "^3.0.0", "eslint-utils": "^2.0.0", "ignore": "^5.1.1", "minimatch": "^3.0.4", "resolve": "^1.10.1", "semver": "^6.1.0" }, "engines": { "node": ">=8.10.0" } }, "node_modules/eslint-plugin-node/node_modules/ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/eslint-plugin-node/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/eslint-plugin-prefer-arrow": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.2.tgz", "integrity": "sha512-C8YMhL+r8RMeMdYAw/rQtE6xNdMulj+zGWud/qIGnlmomiPRaLDGLMeskZ3alN6uMBojmooRimtdrXebLN4svQ==", "dev": true }, "node_modules/eslint-plugin-promise": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/eslint-plugin-standard": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", "dev": true }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/eslint-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "dependencies": { "eslint-visitor-keys": "^1.1.0" }, "engines": { "node": ">=6" } }, "node_modules/eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "engines": { "node": ">=10" } }, "node_modules/eslint/node_modules/semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true, "bin": { "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "dependencies": { "acorn": "^7.4.0", "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" }, "engines": { "node": ">=4" } }, "node_modules/esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "dependencies": { "estraverse": "^5.1.0" }, "engines": { "node": ">=0.10" } }, "node_modules/esquery/node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" } }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "dependencies": { "estraverse": "^5.2.0" }, "engines": { "node": ">=4.0" } }, "node_modules/esrecurse/node_modules/estraverse": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true, "engines": { "node": ">=4.0" } }, "node_modules/estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "engines": { "node": ">=4.0" } }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { "flat-cache": "^3.0.4" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "dependencies": { "locate-path": "^2.0.0" }, "engines": { "node": ">=4" } }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, "bin": { "flat": "cli.js" } }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, "optional": true, "os": [ "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/git-hooks": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/git-hooks/-/git-hooks-1.1.10.tgz", "integrity": "sha512-23nto1qLsqJdY0daAUYqeKPyTvzefR2wZv1krgFRnPg0FxrmITCeWAIv/QfR+fAD7eZC7Rp5OSicJB4hDKyp1A==", "dev": true, "engines": { "node": ">=0.8.x" } }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "dependencies": { "is-glob": "^4.0.1" }, "engines": { "node": ">= 6" } }, "node_modules/globals": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true, "engines": { "node": ">=4.x" } }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "dependencies": { "function-bind": "^1.1.1" }, "engines": { "node": ">= 0.4.0" } }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true, "engines": { "node": ">=4" } }, "node_modules/has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "bin": { "he": "bin/he" } }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true, "engines": { "node": ">=0.8.19" } }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, "engines": { "node": ">=8" } }, "node_modules/is-callable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, "engines": { "node": ">=0.10.0" } }, "node_modules/is-negative-zero": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "engines": { "node": ">=0.12.0" } }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/is-regex": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "dependencies": { "has-symbols": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/is-string": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "dependencies": { "has-symbols": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, "node_modules/json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", "pify": "^2.0.0", "strip-bom": "^3.0.0" }, "engines": { "node": ">=4" } }, "node_modules/locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" }, "engines": { "node": ">=4" } }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, "engines": { "node": "*" } }, "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/mocha": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "4.2.1", "ms": "2.1.3", "nanoid": "3.3.1", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "bin": { "_mocha": "bin/_mocha", "mocha": "bin/mocha" }, "engines": { "node": ">= 12.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mochajs" } }, "node_modules/mocha/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mocha/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mocha/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/mocha/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "node_modules/mocha/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { "p-locate": "^5.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mocha/node_modules/minimatch": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, "engines": { "node": ">=10" } }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node_modules/mocha/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mocha/node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { "p-limit": "^3.0.2" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mocha/node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "dependencies": { "has-flag": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/module-alias": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==", "dev": true }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "node_modules/nanoid": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" }, "engines": { "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "node_modules/nock": { "version": "12.0.3", "resolved": "https://registry.npmjs.org/nock/-/nock-12.0.3.tgz", "integrity": "sha512-QNb/j8kbFnKCiyqi9C5DD0jH/FubFGj5rt9NQFONXwQm3IPB0CULECg/eS3AU1KgZb/6SwUa4/DTRKhVxkGABw==", "dev": true, "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", "lodash": "^4.17.13", "propagate": "^2.0.0" }, "engines": { "node": ">= 10.13" } }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dependencies": { "whatwg-url": "^5.0.0" }, "engines": { "node": "4.x || >=6.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "peerDependenciesMeta": { "encoding": { "optional": true } } }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/oauth-1.0a": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/oauth-1.0a/-/oauth-1.0a-2.2.6.tgz", "integrity": "sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==" }, "node_modules/object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/object.assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.0", "has-symbols": "^1.0.1", "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.assign/node_modules/es-abstract": { "version": "1.18.0-next.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", "dev": true, "dependencies": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", "is-negative-zero": "^2.0.0", "is-regex": "^1.1.1", "object-inspect": "^1.8.0", "object-keys": "^1.1.1", "object.assign": "^4.1.1", "string.prototype.trimend": "^1.0.1", "string.prototype.trimstart": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.values": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" }, "engines": { "node": ">= 0.4" } }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "dependencies": { "wrappy": "1" } }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.3" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "dependencies": { "p-try": "^1.0.0" }, "engines": { "node": ">=4" } }, "node_modules/p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "dependencies": { "p-limit": "^1.1.0" }, "engines": { "node": ">=4" } }, "node_modules/p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true, "engines": { "node": ">=4" } }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "dependencies": { "callsites": "^3.0.0" }, "engines": { "node": ">=6" } }, "node_modules/parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "dependencies": { "error-ex": "^1.2.0" }, "engines": { "node": ">=0.10.0" } }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true, "engines": { "node": ">=4" } }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "node_modules/path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "dependencies": { "pify": "^2.0.0" }, "engines": { "node": ">=4" } }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "engines": { "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "dependencies": { "find-up": "^2.1.0" }, "engines": { "node": ">=4" } }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, "engines": { "node": ">=0.4.0" } }, "node_modules/propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true, "engines": { "node": ">= 8" } }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } }, "node_modules/read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "dependencies": { "load-json-file": "^2.0.0", "normalize-package-data": "^2.3.2", "path-type": "^2.0.0" }, "engines": { "node": ">=4" } }, "node_modules/read-pkg-up": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "dependencies": { "find-up": "^2.0.0", "read-pkg": "^2.0.0" }, "engines": { "node": ">=4" } }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "dependencies": { "picomatch": "^2.2.1" }, "engines": { "node": ">=8.10.0" } }, "node_modules/regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "dependencies": { "path-parse": "^1.0.6" } }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ] }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true, "bin": { "semver": "bin/semver" } }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "dependencies": { "randombytes": "^2.1.0" } }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/should": { "version": "13.2.3", "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", "dev": true, "dependencies": { "should-equal": "^2.0.0", "should-format": "^3.0.3", "should-type": "^1.4.0", "should-type-adaptors": "^1.0.1", "should-util": "^1.0.0" } }, "node_modules/should-equal": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", "dev": true, "dependencies": { "should-type": "^1.4.0" } }, "node_modules/should-format": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", "dev": true, "dependencies": { "should-type": "^1.3.0", "should-type-adaptors": "^1.0.1" } }, "node_modules/should-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", "dev": true }, "node_modules/should-type-adaptors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", "dev": true, "dependencies": { "should-type": "^1.3.0", "should-util": "^1.0.0" } }, "node_modules/should-util": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", "dev": true }, "node_modules/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "node_modules/spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/spdx-license-ids": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", "dev": true }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, "node_modules/string.prototype.trimend": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "node_modules/string.prototype.trimstart": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true, "engines": { "node": ">=4" } }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "dependencies": { "has-flag": "^3.0.0" }, "engines": { "node": ">=4" } }, "node_modules/table": { "version": "6.7.3", "resolved": "https://registry.npmjs.org/table/-/table-6.7.3.tgz", "integrity": "sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw==", "dev": true, "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=10.0.0" } }, "node_modules/table/node_modules/ajv": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.1.tgz", "integrity": "sha512-6CiMNDrzv0ZR916u2T+iRunnD60uWmNn8SkdB44/6stVORUg0aAkWO7PkOhpCmjmW8f2I/G/xnowD66fxGyQJg==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/table/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "dependencies": { "is-number": "^7.0.0" }, "engines": { "node": ">=8.0" } }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, "node_modules/tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.1", "minimist": "^1.2.0", "strip-bom": "^3.0.0" } }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "dependencies": { "punycode": "^2.1.0" } }, "node_modules/v8-compile-cache": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "dev": true }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" }, "engines": { "node": ">= 8" } }, "node_modules/wikibase-sdk": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/wikibase-sdk/-/wikibase-sdk-8.0.0.tgz", "integrity": "sha512-t2mKvASLPqkDNk/CY1z4boB9F/7JsRls9qLXftO2xHAvUVzAcBD/aTjhzwRMmvLRcHfoo38cOlo8J2/tN0yTMA==", "engines": { "node": ">= 10.0.0" } }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/workerpool": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "engines": { "node": ">=10" } }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" }, "engines": { "node": ">=10" } }, "node_modules/yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true, "engines": { "node": ">=10" } }, "node_modules/yargs-unparser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" }, "engines": { "node": ">=10" } }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } } }, "dependencies": { "@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "requires": { "@babel/highlight": "^7.10.4" } }, "@babel/helper-validator-identifier": { "version": "7.15.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, "@babel/highlight": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.15.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" } }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { "color-name": "1.1.3" } }, "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true } } }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" } }, "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.0", "debug": "^4.1.1", "minimatch": "^3.0.4" } }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "requires": {} }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" } }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "~1.0.2" } }, "array-includes": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0", "is-string": "^1.0.5" } }, "array.prototype.flat": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" } }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { "fill-range": "^7.0.1" } }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "camelcase": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", "dev": true }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "dependencies": { "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", "fsevents": "~2.3.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" } }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "config": { "version": "1.31.0", "resolved": "https://registry.npmjs.org/config/-/config-1.31.0.tgz", "integrity": "sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA==", "dev": true, "requires": { "json5": "^1.0.1" } }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, "cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", "requires": { "node-fetch": "2.6.7" } }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "crypto-js": { "version": "3.1.9-1", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" }, "dependencies": { "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, "decamelize": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { "object-keys": "^1.0.12" } }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { "esutils": "^2.0.2" } }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { "ansi-colors": "^4.1.1" } }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, "es-abstract": { "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", "is-regex": "^1.1.1", "object-inspect": "^1.8.0", "object-keys": "^1.1.1", "object.assign": "^4.1.1", "string.prototype.trimend": "^1.0.1", "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", "is-symbol": "^1.0.2" } }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", "@humanwhocodes/config-array": "^0.5.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", "eslint-scope": "^5.1.1", "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", "espree": "^7.3.1", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.1.2", "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", "progress": "^2.0.0", "regexpp": "^3.1.0", "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } } }, "eslint-config-standard": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz", "integrity": "sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg==", "dev": true }, "eslint-import-resolver-node": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, "requires": { "debug": "^2.6.9", "resolve": "^1.13.1" }, "dependencies": { "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" } } } }, "eslint-module-utils": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "dev": true, "requires": { "debug": "^2.6.9", "pkg-dir": "^2.0.0" }, "dependencies": { "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" } } } }, "eslint-plugin-es": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "requires": { "eslint-utils": "^2.0.0", "regexpp": "^3.0.0" } }, "eslint-plugin-import": { "version": "2.22.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", "dev": true, "requires": { "array-includes": "^3.1.1", "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", "eslint-import-resolver-node": "^0.3.4", "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", "resolve": "^1.17.0", "tsconfig-paths": "^3.9.0" }, "dependencies": { "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" } }, "doctrine": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { "esutils": "^2.0.2", "isarray": "^1.0.0" } } } }, "eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "requires": { "eslint-plugin-es": "^3.0.0", "eslint-utils": "^2.0.0", "ignore": "^5.1.1", "minimatch": "^3.0.4", "resolve": "^1.10.1", "semver": "^6.1.0" }, "dependencies": { "ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "eslint-plugin-prefer-arrow": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow/-/eslint-plugin-prefer-arrow-1.2.2.tgz", "integrity": "sha512-C8YMhL+r8RMeMdYAw/rQtE6xNdMulj+zGWud/qIGnlmomiPRaLDGLMeskZ3alN6uMBojmooRimtdrXebLN4svQ==", "dev": true }, "eslint-plugin-promise": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", "dev": true }, "eslint-plugin-standard": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", "dev": true }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "eslint-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, "espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { "acorn": "^7.4.0", "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" } }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { "estraverse": "^5.2.0" }, "dependencies": { "estraverse": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } }, "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { "flat-cache": "^3.0.4" } }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { "to-regex-range": "^5.0.1" } }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { "locate-path": "^2.0.0" } }, "flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { "flatted": "^3.1.0", "rimraf": "^3.0.2" } }, "flatted": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "dev": true }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "git-hooks": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/git-hooks/-/git-hooks-1.1.10.tgz", "integrity": "sha512-23nto1qLsqJdY0daAUYqeKPyTvzefR2wZv1krgFRnPg0FxrmITCeWAIv/QfR+fAD7eZC7Rp5OSicJB4hDKyp1A==", "dev": true }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" } }, "globals": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { "function-bind": "^1.1.1" } }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { "binary-extensions": "^2.0.0" } }, "is-callable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", "dev": true }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" } }, "is-negative-zero": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", "dev": true }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, "is-regex": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" } }, "is-string": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", "dev": true }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { "has-symbols": "^1.0.1" } }, "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" } }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { "minimist": "^1.2.0" } }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", "pify": "^2.0.0", "strip-bom": "^3.0.0" } }, "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" } }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "mocha": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "4.2.1", "ms": "2.1.3", "nanoid": "3.3.1", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "dependencies": { "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { "argparse": "^2.0.1" } }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { "p-locate": "^5.0.0" } }, "minimatch": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { "yocto-queue": "^0.1.0" } }, "p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { "p-limit": "^3.0.2" } }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, "module-alias": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==", "dev": true }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "nanoid": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "nock": { "version": "12.0.3", "resolved": "https://registry.npmjs.org/nock/-/nock-12.0.3.tgz", "integrity": "sha512-QNb/j8kbFnKCiyqi9C5DD0jH/FubFGj5rt9NQFONXwQm3IPB0CULECg/eS3AU1KgZb/6SwUa4/DTRKhVxkGABw==", "dev": true, "requires": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", "lodash": "^4.17.13", "propagate": "^2.0.0" } }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "requires": { "whatwg-url": "^5.0.0" } }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, "oauth-1.0a": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/oauth-1.0a/-/oauth-1.0a-2.2.6.tgz", "integrity": "sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==" }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, "object.assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.0", "has-symbols": "^1.0.1", "object-keys": "^1.1.1" }, "dependencies": { "es-abstract": { "version": "1.18.0-next.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", "is-negative-zero": "^2.0.0", "is-regex": "^1.1.1", "object-inspect": "^1.8.0", "object-keys": "^1.1.1", "object.assign": "^4.1.1", "string.prototype.trimend": "^1.0.1", "string.prototype.trimstart": "^1.0.1" } } } }, "object.values": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" } }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.3" } }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { "p-try": "^1.0.0" } }, "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { "p-limit": "^1.1.0" } }, "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { "callsites": "^3.0.0" } }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { "error-ex": "^1.2.0" } }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { "pify": "^2.0.0" } }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { "find-up": "^2.1.0" } }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "propagate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { "safe-buffer": "^5.1.0" } }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { "load-json-file": "^2.0.0", "normalize-package-data": "^2.3.2", "path-type": "^2.0.0" } }, "read-pkg-up": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { "find-up": "^2.0.0", "read-pkg": "^2.0.0" } }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { "picomatch": "^2.2.1" } }, "regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" } }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" } }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { "randombytes": "^2.1.0" } }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { "shebang-regex": "^3.0.0" } }, "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "should": { "version": "13.2.3", "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", "dev": true, "requires": { "should-equal": "^2.0.0", "should-format": "^3.0.3", "should-type": "^1.4.0", "should-type-adaptors": "^1.0.1", "should-util": "^1.0.0" } }, "should-equal": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", "dev": true, "requires": { "should-type": "^1.4.0" } }, "should-format": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", "dev": true, "requires": { "should-type": "^1.3.0", "should-type-adaptors": "^1.0.1" } }, "should-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", "dev": true }, "should-type-adaptors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", "dev": true, "requires": { "should-type": "^1.3.0", "should-util": "^1.0.0" } }, "should-util": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", "dev": true }, "slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", "dev": true }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "string.prototype.trimend": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "string.prototype.trimstart": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { "ansi-regex": "^5.0.1" } }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" } }, "table": { "version": "6.7.3", "resolved": "https://registry.npmjs.org/table/-/table-6.7.3.tgz", "integrity": "sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw==", "dev": true, "requires": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" }, "dependencies": { "ajv": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.1.tgz", "integrity": "sha512-6CiMNDrzv0ZR916u2T+iRunnD60uWmNn8SkdB44/6stVORUg0aAkWO7PkOhpCmjmW8f2I/G/xnowD66fxGyQJg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true } } }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { "is-number": "^7.0.0" } }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", "dev": true, "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.1", "minimist": "^1.2.0", "strip-bom": "^3.0.0" } }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { "prelude-ls": "^1.2.1" } }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" } }, "v8-compile-cache": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "dev": true }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, "wikibase-sdk": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/wikibase-sdk/-/wikibase-sdk-8.0.0.tgz", "integrity": "sha512-t2mKvASLPqkDNk/CY1z4boB9F/7JsRls9qLXftO2xHAvUVzAcBD/aTjhzwRMmvLRcHfoo38cOlo8J2/tN0yTMA==" }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, "workerpool": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true }, "yargs-unparser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" } }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true } } } wikibase-edit-5.3.0/package.json000066400000000000000000000032541433026310300165250ustar00rootroot00000000000000{ "name": "wikibase-edit", "version": "5.3.0", "description": "Edit Wikibase from NodeJS", "main": "lib/index.js", "files": [ "lib" ], "scripts": { "lint": "eslint -c .eslintrc lib tests", "test": "npm run test:unit && npm run test:integration", "test:unit": "mocha $MOCHA_OPTIONS tests/unit/*.js tests/unit/*/*.js", "test:integration": "mocha $MOCHA_OPTIONS tests/integration/*.js tests/integration/*/*.js", "prepublishOnly": "npm run lint && npm test", "postpublish": "git push --tags", "update-toc": "./scripts/update_toc" }, "repository": { "type": "git", "url": "git+https://github.com/maxlath/wikibase-edit.git" }, "keywords": [ "wikibase", "wikidata", "write", "update", "edit", "API" ], "author": "maxlath", "license": "MIT", "bugs": { "url": "https://github.com/maxlath/wikibase-edit/issues" }, "homepage": "https://github.com/maxlath/wikibase-edit", "dependencies": { "cross-fetch": "^3.0.4", "crypto-js": "^3.1.9-1", "lodash.isequal": "^4.5.0", "oauth-1.0a": "^2.2.6", "wikibase-sdk": "^8.0.0" }, "devDependencies": { "config": "^1.31.0", "eslint": "^7.10.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prefer-arrow": "^1.2.2", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", "git-hooks": "^1.1.10", "mocha": "^9.1.3", "module-alias": "^2.2.2", "nock": "^12.0.3", "should": "^13.2.3" }, "engines": { "node": ">= 10.0.0" }, "_moduleAliases": { "lib": "lib", "root": ".", "tests": "tests" } } wikibase-edit-5.3.0/scripts/000077500000000000000000000000001433026310300157225ustar00rootroot00000000000000wikibase-edit-5.3.0/scripts/update_toc000077500000000000000000000004611433026310300200000ustar00rootroot00000000000000#!/usr/bin/env bash which doctoc && { doctoc docs/*.md } || { echo "requires to have https://www.npmjs.com/package/doctoc installed, either globally or just in this repo" echo "(it is not installed as a dev dependency as the use made of it is not worth the subdependencies maintainance)" exit 1 } wikibase-edit-5.3.0/tests/000077500000000000000000000000001433026310300153755ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/000077500000000000000000000000001433026310300177205ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/alias/000077500000000000000000000000001433026310300210115ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/alias/add.js000066400000000000000000000016401433026310300221000ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const { getSandboxItemId } = require('tests/integration/utils/sandbox_entities') const language = 'fr' describe('alias add', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should add an alias', async () => { const id = await getSandboxItemId() const value = randomString() const res = await wbEdit.alias.add({ id, language, value }) res.success.should.equal(1) }) it('should add several aliases', async () => { const id = await getSandboxItemId() const aliases = [ randomString(), randomString(), randomString(), randomString() ] const res = await wbEdit.alias.add({ id, language, value: aliases }) res.success.should.equal(1) }) }) wikibase-edit-5.3.0/tests/integration/alias/remove.js000066400000000000000000000011561433026310300226470ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const { getSandboxItemId } = require('tests/integration/utils/sandbox_entities') const language = 'fr' describe('alias remove', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should remove an alias', async () => { const id = await getSandboxItemId() const value = randomString() const res = await wbEdit.alias.remove({ id, language, value }) res.success.should.equal(1) }) }) wikibase-edit-5.3.0/tests/integration/alias/set.js000066400000000000000000000011451433026310300221430ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const { getSandboxItemId } = require('tests/integration/utils/sandbox_entities') const language = 'fr' describe('alias set', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should set an alias', async () => { const id = await getSandboxItemId() const value = randomString() const res = await wbEdit.alias.set({ id, language, value }) res.success.should.equal(1) }) }) wikibase-edit-5.3.0/tests/integration/anonymous_edit.js000066400000000000000000000020201433026310300233050ustar00rootroot00000000000000require('should') const { instance } = require('config') const { randomString } = require('tests/unit/utils') const WBEdit = require('root') const { getSandboxItemId } = require('tests/integration/utils/sandbox_entities') describe('anonymous edit', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should make an anonymous edit when general config has anonymous=true', async () => { const wbEdit = WBEdit({ instance, anonymous: true }) const id = await getSandboxItemId() const value = randomString() const res = await wbEdit.alias.add({ id, language: 'fr', value }) res.success.should.equal(1) }) it('should make an anonymous edit when request config has anonymous=true', async () => { const wbEdit = WBEdit({ instance }) const id = await getSandboxItemId() const value = randomString() const res = await wbEdit.alias.add({ id, language: 'fr', value }, { anonymous: true }) res.success.should.equal(1) }) }) wikibase-edit-5.3.0/tests/integration/badge/000077500000000000000000000000001433026310300207625ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/badge/add.js000066400000000000000000000020621433026310300220500ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) // Those tests require setting an instance with sitelinks // (such as test.wikidata.org) in config, and are thus disabled by default xdescribe('add badges', () => { it('should add a badge', async () => { await wbEdit.sitelink.set({ id: 'Q224124', site: 'dewiki', title: 'September', badges: [ 'Q608' ] }) const res = await wbEdit.badge.add({ id: 'Q224124', site: 'dewiki', badges: [ 'Q609' ] }) res.success.should.equal(1) res.entity.sitelinks.dewiki.badges.should.deepEqual([ 'Q608', 'Q609' ]) }) it('should ignore already added badges', async () => { await wbEdit.sitelink.set({ id: 'Q224124', site: 'dewiki', title: 'September', badges: [ 'Q608' ] }) const res = await wbEdit.badge.add({ id: 'Q224124', site: 'dewiki', badges: [ 'Q608' ] }) res.success.should.equal(1) res.entity.sitelinks.dewiki.badges.should.deepEqual([ 'Q608' ]) }) }) wikibase-edit-5.3.0/tests/integration/badge/remove.js000066400000000000000000000020471433026310300226200ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) // Those tests require setting an instance with sitelinks // (such as test.wikidata.org) in config, and are thus disabled by default xdescribe('remove badges', () => { it('should remove a badge', async () => { await wbEdit.sitelink.set({ id: 'Q224124', site: 'dewiki', title: 'September', badges: [ 'Q608' ] }) const res = await wbEdit.badge.remove({ id: 'Q224124', site: 'dewiki', badges: [ 'Q608' ] }) res.success.should.equal(1) res.entity.sitelinks.dewiki.badges.should.deepEqual([]) }) it('should ignore absent badges', async () => { await wbEdit.sitelink.set({ id: 'Q224124', site: 'dewiki', title: 'September', badges: [ 'Q608' ] }) const res = await wbEdit.badge.remove({ id: 'Q224124', site: 'dewiki', badges: [ 'Q609' ] }) res.success.should.equal(1) res.entity.sitelinks.dewiki.badges.should.deepEqual([ 'Q608' ]) }) }) wikibase-edit-5.3.0/tests/integration/baserevid.js000066400000000000000000000120131433026310300222170ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('../unit/utils') const { shouldNotBeCalled } = require('./utils/utils') const { getSandboxItem, getRefreshedEntity, getSandboxItemId, getSandboxPropertyId } = require('./utils/sandbox_entities') const { addClaim, addQualifier } = require('./utils/sandbox_snaks') const getProperty = require('./utils/get_property') describe('baserevid', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should accept a valid baserevid', async () => { const { id } = await getSandboxItem() const { lastrevid } = await getRefreshedEntity(id) const res = await wbEdit.label.set({ id, language: 'fr', value: randomString(), }, { baserevid: lastrevid }) res.success.should.equal(1) res.entity.lastrevid.should.equal(lastrevid + 1) }) it('should pass baserevid from request config', async () => { const { id } = await getSandboxItem() await wbEdit.label.set({ id, language: 'fr', value: randomString(), }, { baserevid: 1 }) .then(shouldNotBeCalled) .catch(err => { err.body.error.code.should.equal('cant-load-entity-content') }) }) it('should pass baserevid from edit object', async () => { const { id } = await getSandboxItem() await wbEdit.entity.edit({ id, labels: { la: randomString() }, baserevid: 1 }) .then(shouldNotBeCalled) .catch(err => { err.body.error.code.should.equal('cant-load-entity-content') }) }) describe('request wrapper commands', () => { it('should pass baserevid from commands object', async () => { const { id } = await getSandboxItem() await wbEdit.label.set({ id, language: 'fr', value: randomString(), baserevid: 1, }) .then(shouldNotBeCalled) .catch(err => { err.body.error.code.should.equal('cant-load-entity-content') }) }) }) describe('bundle wrapper commands', () => { it('should pass a baserevid in claim.create', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('string') ]) const value = randomString() await wbEdit.claim.create({ id, property, value, baserevid: 1 }) .then(shouldNotBeCalled) .catch(err => { err.body.error.code.should.equal('cant-load-entity-content') }) }) it('should pass a baserevid in claim.move when targeting a single entity', async () => { const { id, guid } = await addClaim({ datatype: 'quantity', value: 123 }) const stringProperty = await getSandboxPropertyId('string') await wbEdit.claim.move({ guid, id, property: stringProperty, baserevid: 1 }) .then(shouldNotBeCalled) .catch(err => { err.body.error.code.should.equal('cant-load-entity-content') }) }) it('should not allow to pass a baserevid in claim.move when edit targeting multiple entities', async () => { const { property, guid } = await addClaim() await wbEdit.claim.move({ guid, id: 'Q1', property, baserevid: 1 }) .then(shouldNotBeCalled) .catch(err => { err.message.should.equal('commands editing multiple entities can not have a baserevid') }) }) it('should pass a baserevid in claim.update', async () => { const oldValue = randomString() const newValue = randomString() const { guid } = await addClaim({ datatype: 'string', value: oldValue }) await wbEdit.claim.update({ guid, newValue, baserevid: 1 }) .then(shouldNotBeCalled) .catch(err => { err.body.error.code.should.equal('cant-load-entity-content') }) }) it('should pass a baserevid in qualifier.move', async () => { const [ valueA, valueB ] = [ randomString(), randomString() ] const { id: oldProperty } = await getProperty({ datatype: 'string', reserved: true }) const { guid, hash } = await addQualifier({ property: oldProperty, value: valueA }) await addQualifier({ guid, property: oldProperty, value: valueB }) const { id: newProperty } = await getProperty({ datatype: 'string', reserved: true }) await wbEdit.qualifier.move({ guid, hash, oldProperty, newProperty, baserevid: 1 }) .then(shouldNotBeCalled) .catch(err => { err.body.error.code.should.equal('cant-load-entity-content') }) }) it('should pass a baserevid in qualifier.update', async () => { const oldValue = randomString() const newValue = randomString() const { guid, property } = await addQualifier({ datatype: 'string', value: oldValue }) await wbEdit.qualifier.update({ guid, property, oldValue, newValue, baserevid: 1 }) .then(shouldNotBeCalled) .catch(err => { err.body.error.code.should.equal('cant-load-entity-content') }) }) }) }) wikibase-edit-5.3.0/tests/integration/claim/000077500000000000000000000000001433026310300210055ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/claim/create.js000066400000000000000000000236051433026310300226140ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const { shouldNotBeCalled } = require('tests/integration/utils/utils') const { getSandboxPropertyId, getSandboxItemId } = require('tests/integration/utils/sandbox_entities') const { isGuid } = require('wikibase-sdk') describe('claim create', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should create a claim', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('string') ]) const value = randomString() const res = await wbEdit.claim.create({ id, property, value }) res.success.should.equal(1) isGuid(res.claim.id).should.be.true() res.claim.rank.should.equal('normal') res.claim.mainsnak.snaktype.should.equal('value') res.claim.mainsnak.datavalue.value.should.equal(value) }) it('should create a claim with a negative year', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('time') ]) const res = await wbEdit.claim.create({ id, property, value: '-0028' }) res.success.should.equal(1) isGuid(res.claim.id).should.be.true() res.claim.rank.should.equal('normal') res.claim.mainsnak.snaktype.should.equal('value') res.claim.mainsnak.datavalue.value.time.should.equal('-0028-00-00T00:00:00Z') }) it('should create a claim with a preferred rank', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('string') ]) const value = randomString() const res = await wbEdit.claim.create({ id, property, value, rank: 'preferred' }) res.claim.rank.should.equal('preferred') }) it('should create a claim with qualifiers and references', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('string') ]) const value = randomString() const res = await wbEdit.claim.create({ id, property, value, rank: 'preferred', qualifiers: { [property]: value }, references: [ { [property]: value }, { [property]: value } ] }) res.claim.qualifiers[property][0].datavalue.value.should.equal(value) res.claim.references[0].snaks[property][0].datavalue.value.should.equal(value) res.claim.references[1].snaks[property][0].datavalue.value.should.equal(value) }) it('should create a time claim with a low precision', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('time') ]) const value = { time: '2500000', precision: 4 } const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.time.should.equal('+2500000-00-00T00:00:00Z') res.claim.mainsnak.datavalue.value.precision.should.equal(4) }) // time precision not supported by the Wikibase API xit('should create a time claim with a high precision', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('time') ]) const value = { time: '1802-02-04T11:22:33Z', precision: 14 } const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.time.should.equal('+1802-02-04T11:22:33Z') res.claim.mainsnak.datavalue.value.precision.should.equal(14) }) it('should create a time claim with a custom calendar', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('time') ]) const value = { time: '1402-11-12', calendar: 'julian' } const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.time.should.equal('+1402-11-12T00:00:00Z') res.claim.mainsnak.datavalue.value.calendarmodel.should.equal('http://www.wikidata.org/entity/Q1985786') }) it('should reject a claim with an invalid time', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('time') ]) const value = '1802-22-33' try { await wbEdit.claim.create({ id, property, value }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('invalid time value') err.context.value.should.equal(value) } }) it('should create a external id claim', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('external-id') ]) const value = 'foo' const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.should.equal('foo') }) it('should create a monolingualtext claim', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('monolingualtext') ]) const value = { text: 'bulgroz', language: 'fr' } const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.text.should.equal('bulgroz') res.claim.mainsnak.datavalue.value.language.should.equal('fr') }) it('should create a url claim', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('url') ]) const value = 'http://foo.bar' const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.should.equal(value) }) it('should create a quantity claim from a positive number value', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('quantity') ]) const value = 9000 const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.amount.should.equal('+9000') res.claim.mainsnak.datavalue.value.unit.should.equal('1') }) it('should create a quantity claim from a negative number value', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('quantity') ]) const value = -9000 const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.amount.should.equal('-9000') res.claim.mainsnak.datavalue.value.unit.should.equal('1') }) it('should create a quantity claim from a positive string value', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('quantity') ]) const value = '9001' const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.amount.should.equal('+9001') res.claim.mainsnak.datavalue.value.unit.should.equal('1') }) it('should create a quantity claim from a negative string value', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('quantity') ]) const value = '-9001' const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.amount.should.equal('-9001') res.claim.mainsnak.datavalue.value.unit.should.equal('1') }) it('should create a quantity claim with a custom unit and bounds', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('quantity') ]) const value = { amount: 9002, unit: 'Q7727', lowerBound: 9001, upperBound: 9013 } const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.amount.should.equal('+9002') res.claim.mainsnak.datavalue.value.lowerBound.should.equal('+9001') res.claim.mainsnak.datavalue.value.upperBound.should.equal('+9013') res.claim.mainsnak.datavalue.value.unit.should.endWith('Q7727') }) it('should create a globe coordinate claim', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('globe-coordinate') ]) const value = { latitude: 45.758, longitude: 4.84138, precision: 1 / 360 } const res = await wbEdit.claim.create({ id, property, value }) const createdValue = res.claim.mainsnak.datavalue.value createdValue.latitude.should.equal(value.latitude) createdValue.longitude.should.equal(value.longitude) createdValue.precision.should.equal(value.precision) }) it('should create a geo-shape claim', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('geo-shape') ]) const value = 'Data:United Kingdom.map' const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.should.equal(value) }) it('should create a tabular-data claim', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('tabular-data') ]) const value = 'Data:Sandbox/TheDJ/DJ.tab' const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.datavalue.value.should.equal(value) }) // Math and musical notation datatype aren't available on the wikibase-docker instance. it('should create a claim of snaktype novalue', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('url') ]) const value = { snaktype: 'novalue' } const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.snaktype.should.equal('novalue') }) it('should create a claim of snaktype somevalue', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('url') ]) const value = { snaktype: 'somevalue' } const res = await wbEdit.claim.create({ id, property, value }) res.claim.mainsnak.snaktype.should.equal('somevalue') }) }) wikibase-edit-5.3.0/tests/integration/claim/move_claim.js000066400000000000000000000233561433026310300234670ustar00rootroot00000000000000const should = require('should') const config = require('config') const wbEdit = require('root')(config) const { move: moveClaim } = wbEdit.claim const { shouldNotBeCalled, getLastEditSummary } = require('tests/integration/utils/utils') const { createItem, getSomeEntityId, getSomeGuid } = require('tests/integration/utils/sandbox_entities') const { addClaim } = require('tests/integration/utils/sandbox_snaks') const { randomString } = require('tests/unit/utils') const getProperty = require('tests/integration/utils/get_property') describe('move claim', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should reject missing guid', async () => { try { await moveClaim({}).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('missing claim guid or property claims id') } }) it('should reject invalid guid', async () => { try { await moveClaim({ guid: 123 }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('invalid claim guid') } }) it('should reject missing entity id', async () => { try { const guid = await getSomeGuid() await moveClaim({ guid }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('missing target entity id') } }) it('should reject invalid entity id', async () => { try { const guid = await getSomeGuid() await moveClaim({ guid, id: 'foo' }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('invalid target entity id') } }) it('should reject missing property', async () => { try { const guid = await getSomeGuid() const id = await getSomeEntityId() await moveClaim({ guid, id }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('missing property id') } }) it('should reject invalid property', async () => { try { const guid = await getSomeGuid() const id = await getSomeEntityId() await moveClaim({ guid, id, property: '123' }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('invalid property id') } }) it('should move a claim from one property to another', async () => { const { id } = await createItem() const { guid, property: currentProperty } = await addClaim({ id, datatype: 'string', value: randomString() }) const { id: otherStringPropertyId } = await getProperty({ datatype: 'string', reserved: true }) const [ res ] = await moveClaim({ guid, id, property: otherStringPropertyId }) const { entity } = res entity.id.should.equal(id) should(entity.claims[currentProperty]).not.be.ok() const movedClaim = entity.claims[otherStringPropertyId][0] movedClaim.id.should.not.equal(guid) }) it('should move only the claim identified with a guid', async () => { const { id } = await createItem() const { guid, property: currentProperty } = await addClaim({ id, datatype: 'string', value: randomString() }) const { guid: otherClaimGuid } = await addClaim({ id, property: currentProperty, value: randomString() }) const { id: otherStringPropertyId } = await getProperty({ datatype: 'string', reserved: true }) const [ res ] = await moveClaim({ guid, id, property: otherStringPropertyId }) const { entity } = res entity.id.should.equal(id) entity.claims[currentProperty][0].id.should.equal(otherClaimGuid) const movedClaim = entity.claims[otherStringPropertyId][0] movedClaim.id.should.not.equal(guid) }) it('should generate a custom summary', async () => { const { id } = await createItem() const { guid, property: currentProperty } = await addClaim({ id, datatype: 'string', value: randomString() }) const { id: otherStringPropertyId } = await getProperty({ datatype: 'string', reserved: true }) const [ res ] = await moveClaim({ guid, id, property: otherStringPropertyId }) const summary = await getLastEditSummary(res) summary.split('*/')[1].trim() .should.equal(`moving a ${currentProperty} claim to ${otherStringPropertyId}`) }) it("should reject if properties datatypes don't match (and don't have a type converter)", async () => { const { guid, id, property } = await addClaim({ datatype: 'string', value: randomString() }) const { id: someUnconvertablePropertyId } = await getProperty({ datatype: 'wikibase-item' }) try { await moveClaim({ guid, id, property: someUnconvertablePropertyId }).then(shouldNotBeCalled) } catch (err) { err.message.should.startWith("properties datatype don't match") err.context.originPropertyId.should.equal(property) err.context.targetPropertyId.should.equal(someUnconvertablePropertyId) } }) it('should move a claim from one entity to another', async () => { const { guid, id, property } = await addClaim({ datatype: 'string', value: randomString() }) const { id: otherItemId } = await createItem() const res = await moveClaim({ guid, id: otherItemId, property }) const [ removeClaimRes, addClaimRes ] = res removeClaimRes.entity.id.should.equal(id) addClaimRes.entity.id.should.equal(otherItemId) }) describe('type conversions', () => { describe('string->quantity', () => { it('should convert a positive string number value to quantity', async () => { await testTypeConversion({ originalType: 'string', originalValue: '765.521521', targetType: 'quantity', targetValue: { amount: '+765.521521', unit: '1' }, }) }) it('should convert a signed positive string number value to quantity', async () => { await testTypeConversion({ originalType: 'string', originalValue: '+123.52', targetType: 'quantity', targetValue: { amount: '+123.52', unit: '1' }, }) }) it('should convert a negative string number value to quantity', async () => { await testTypeConversion({ originalType: 'string', originalValue: '-5519.521521', targetType: 'quantity', targetValue: { amount: '-5519.521521', unit: '1' }, }) }) it('should reject to convert a non-number string', async () => { await testTypeConversion({ originalType: 'string', originalValue: '123.abc', targetType: 'quantity' }) .then(shouldNotBeCalled) .catch(err => { err.message.should.equal("properties datatype don't match and string->quantity type conversion failed: invalid string number") }) }) }) describe('quantity->string', () => { it('should convert a positive integer to a string', async () => { await testTypeConversion({ originalType: 'quantity', originalValue: 96, targetType: 'string', targetValue: '96', }) }) it('should convert a positive float to a string', async () => { await testTypeConversion({ originalType: 'quantity', originalValue: 987.456, targetType: 'string', targetValue: '987.456', }) }) it('should convert a negative integer to a string', async () => { await testTypeConversion({ originalType: 'quantity', originalValue: -654, targetType: 'string', targetValue: '-654', }) }) it('should convert a negative float to a string', async () => { await testTypeConversion({ originalType: 'quantity', originalValue: -12.56, targetType: 'string', targetValue: '-12.56', }) }) }) describe('external-id->string', () => { it('should convert an external-id to a string', async () => { const value = randomString() await testTypeConversion({ originalType: 'external-id', originalValue: value, targetType: 'string', targetValue: value, }) }) }) describe('string->external-id', () => { it('should convert a string to an external-id', async () => { const value = randomString() await testTypeConversion({ originalType: 'string', originalValue: value, targetType: 'external-id', targetValue: value, }) }) }) describe('monolingualtext->string', () => { it('should convert a monolingualtext to a string', async () => { const value = randomString() await testTypeConversion({ originalType: 'monolingualtext', originalValue: { text: value, language: 'en' }, targetType: 'string', targetValue: value, }) }) }) describe('monolingualtext->string', () => { it('should not convert a string to a monolingualtext', async () => { await testTypeConversion({ originalType: 'string', originalValue: randomString(), targetType: 'monolingualtext' }) .then(shouldNotBeCalled) .catch(err => { err.message.should.startWith("properties datatype don't match") }) }) }) }) }) let itemId const testTypeConversion = async ({ originalType, originalValue, targetType, targetValue }) => { // It's safe to reuse the same item as we are using claim guids itemId = itemId || (await createItem()).id const { guid } = await addClaim({ id: itemId, datatype: originalType, value: originalValue }) const { id: otherPropertyId } = await getProperty({ datatype: targetType }) const [ { entity } ] = await moveClaim({ guid, id: itemId, property: otherPropertyId }) const movedClaim = entity.claims[otherPropertyId].slice(-1)[0] movedClaim.mainsnak.datatype.should.equal(targetType) movedClaim.mainsnak.datavalue.value.should.deepEqual(targetValue) } wikibase-edit-5.3.0/tests/integration/claim/move_property_claims.js000066400000000000000000000164431433026310300256150ustar00rootroot00000000000000const should = require('should') const config = require('config') const wbEdit = require('root')(config) const { move: movePropertyClaims } = wbEdit.claim const { shouldNotBeCalled, getLastEditSummary } = require('tests/integration/utils/utils') const { createItem, getSomeEntityId } = require('tests/integration/utils/sandbox_entities') const { addClaim } = require('tests/integration/utils/sandbox_snaks') const { randomString } = require('tests/unit/utils') const getProperty = require('tests/integration/utils/get_property') let somePropertyClaimsId describe('move property claims', async () => { before(async () => { const { id: propertyId } = await getProperty({ datatype: 'string' }) somePropertyClaimsId = `Q1#${propertyId}` console.log('somePropertyClaimsId', somePropertyClaimsId) }) it('should reject invalid property claims id', async () => { try { await movePropertyClaims({ propertyClaimsId: 'Q1~P31' }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('invalid property id') } }) it('should reject missing entity id', async () => { try { await movePropertyClaims({ propertyClaimsId: somePropertyClaimsId }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('missing target entity id') } }) it('should reject invalid entity id', async () => { try { await movePropertyClaims({ propertyClaimsId: somePropertyClaimsId, id: 'foo' }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('invalid target entity id') } }) it('should reject missing property', async () => { try { const id = await getSomeEntityId() await movePropertyClaims({ propertyClaimsId: somePropertyClaimsId, id }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('missing property id') } }) it('should reject invalid property', async () => { try { const id = await getSomeEntityId() await movePropertyClaims({ propertyClaimsId: somePropertyClaimsId, id, property: '123' }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('invalid property id') } }) it('should reject move operation with no entity or property change', async () => { const { id, property } = await addClaim({ datatype: 'string', value: randomString() }) const propertyClaimsId = `${id}#${property}` try { await movePropertyClaims({ propertyClaimsId, id, property }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal("move operation wouldn't have any effect: same entity, same property") } }) it("should reject if properties datatypes don't match (and can't be converted)", async () => { const { id, property } = await addClaim({ datatype: 'string', value: randomString() }) const propertyClaimsId = `${id}#${property}` const { id: someQuantityProperty } = await getProperty({ datatype: 'quantity' }) try { await movePropertyClaims({ propertyClaimsId, id, property: someQuantityProperty }).then(shouldNotBeCalled) } catch (err) { err.message.should.startWith("properties datatype don't match") err.context.targetPropertyId.should.equal(someQuantityProperty) err.context.targetDatatype.should.equal('quantity') err.context.originPropertyId.should.equal(property) err.context.originDatatype.should.equal('string') } }) it('should reject moves with no claims to move', async () => { const { id } = await createItem() const { id: someStringPropertyId } = await getProperty({ datatype: 'string', reserved: true }) const { id: otherStringPropertyId } = await getProperty({ datatype: 'string', reserved: true }) const propertyClaimsId = `${id}#${someStringPropertyId}` try { await movePropertyClaims({ propertyClaimsId, id, property: otherStringPropertyId }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('no property claims found') } }) it('should move property claims from one property to another', async () => { const { id } = await createItem() const { guid, property: currentPropertyId } = await addClaim({ id, datatype: 'string', value: randomString() }) const { id: otherStringPropertyId } = await getProperty({ datatype: 'string', reserved: true }) const propertyClaimsId = `${id}#${currentPropertyId}` const res = await movePropertyClaims({ propertyClaimsId, id, property: otherStringPropertyId }) const { entity } = res[0] entity.id.should.equal(id) should(entity.claims[currentPropertyId]).not.be.ok() const movedClaim = entity.claims[otherStringPropertyId][0] movedClaim.id.should.not.equal(guid) }) it('should generate a custom summary', async () => { const { id } = await createItem() const { property: currentPropertyId } = await addClaim({ id, datatype: 'string', value: randomString() }) const { id: otherStringPropertyId } = await getProperty({ datatype: 'string', reserved: true }) const propertyClaimsId = `${id}#${currentPropertyId}` const res = await movePropertyClaims({ propertyClaimsId, id, property: otherStringPropertyId }) const summary = await getLastEditSummary(res[0]) summary.split('*/')[1].trim().should.equal(`moving ${currentPropertyId} claims to ${otherStringPropertyId}`) }) it('should move a claim from one entity to another', async () => { const value = Math.trunc(Math.random() * 1000) const { id: currentPropertyId } = await getProperty({ datatype: 'quantity', reserved: true }) const { guid, id } = await addClaim({ property: currentPropertyId, value }) const propertyClaimsId = `${id}#${currentPropertyId}` const { id: otherItemId } = await createItem() const { id: otherStringPropertyId } = await getProperty({ datatype: 'quantity', reserved: true }) const res = await movePropertyClaims({ propertyClaimsId, id: otherItemId, property: otherStringPropertyId }) const [ removeClaimsRes, addClaimsRes ] = res const { entity: previousEntity } = removeClaimsRes const { entity: newEntity } = addClaimsRes previousEntity.id.should.equal(id) newEntity.id.should.equal(otherItemId) should(previousEntity.claims[currentPropertyId]).not.be.ok() should(previousEntity.claims[otherStringPropertyId]).not.be.ok() should(newEntity.claims[currentPropertyId]).not.be.ok() newEntity.claims[otherStringPropertyId][0].mainsnak.datavalue.value.amount.should.equal(`+${value}`) newEntity.claims[otherStringPropertyId][0].id.should.not.equal(guid) }) it('should convert types', async () => { const { id } = await createItem() const { property: currentPropertyId } = await addClaim({ id, datatype: 'string', value: '123' }) await addClaim({ id, property: currentPropertyId, value: '456' }) const { id: otherPropertyId } = await getProperty({ datatype: 'quantity' }) const propertyClaimsId = `${id}#${currentPropertyId}` const [ { entity } ] = await movePropertyClaims({ propertyClaimsId, id, property: otherPropertyId }) const movedClaims = entity.claims[otherPropertyId] movedClaims[0].mainsnak.datatype.should.equal('quantity') movedClaims[0].mainsnak.datavalue.value.should.deepEqual({ amount: '+123', unit: '1' }) movedClaims[1].mainsnak.datatype.should.equal('quantity') movedClaims[1].mainsnak.datavalue.value.should.deepEqual({ amount: '+456', unit: '1' }) }) }) wikibase-edit-5.3.0/tests/integration/claim/reconciliation.js000066400000000000000000000050071433026310300243470ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { getSandboxPropertyId, getReservedItemId } = require('tests/integration/utils/sandbox_entities') const { simplify } = require('wikibase-sdk') const { shouldNotBeCalled } = require('../utils/utils') describe('reconciliation: general', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should reject a reconciliation object with a typo', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) await wbEdit.claim.create({ id, property, value: 'foo', reconciliationz: {} }) .then(shouldNotBeCalled) .catch(err => { err.message.should.equal('invalid parameter: reconciliationz') }) await wbEdit.entity.edit({ id, claims: { [property]: 'foo' }, reconciliationz: {} }) .then(shouldNotBeCalled) .catch(err => { err.message.should.equal('invalid parameter: reconciliationz') }) await wbEdit.entity.edit({ id, claims: { [property]: { value: 'foo', reconciliationz: {} } }, }) .then(shouldNotBeCalled) .catch(err => { err.message.should.equal('invalid claim parameter: reconciliationz') }) }) describe('per-claim reconciliation settings', () => { it('should accept per-claim reconciliation settings', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'foo', qualifiers: { [property]: 'buzz' } }, { value: 'bar', qualifiers: { [property]: 'bla' } }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'foo', qualifiers: { [property]: 'blo' } }, { value: 'bar', qualifiers: { [property]: 'bli' }, reconciliation: { mode: 'skip-on-value-match' } }, ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims, { keepQualifiers: true }).should.deepEqual({ [property]: [ { value: 'foo', qualifiers: { [property]: [ 'buzz', 'blo' ] } }, { value: 'bar', qualifiers: { [property]: [ 'bla' ] } }, ] }) }) }) }) wikibase-edit-5.3.0/tests/integration/claim/reconciliation_matching.js000066400000000000000000000133261433026310300262240ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { getSandboxPropertyId, getReservedItemId } = require('tests/integration/utils/sandbox_entities') const { simplify } = require('wikibase-sdk') describe('reconciliation: matching', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) describe('qualifiers', () => { it('should match on all specified qualifiers properties by default', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: [ 'bar', 'buzz' ] } }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: [ 'bar' ] }, reconciliation: { mode: 'skip-on-value-match', matchingQualifiers: [ property ] } }) res2.claim.id.should.not.equal(res.claim.id) const res3 = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: [ 'bar', 'buzz', 'bla' ] }, reconciliation: { mode: 'skip-on-value-match', matchingQualifiers: [ property ] } }) res3.claim.id.should.not.equal(res.claim.id) res3.claim.id.should.not.equal(res2.claim.id) }) it('should match on any specified qualifiers properties when requested', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: [ 'bar', 'buzz' ] } }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: [ 'bar' ] }, reconciliation: { mode: 'skip-on-value-match', matchingQualifiers: [ `${property}:any` ] } }) res2.claim.id.should.equal(res.claim.id) const res3 = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: [ 'bar', 'buzz', 'bla' ] }, reconciliation: { mode: 'skip-on-value-match', matchingQualifiers: [ `${property}:any` ] } }) res3.claim.id.should.equal(res.claim.id) }) }) describe('references', () => { it('should match on all specified reference properties by default', async () => { const [ id, property, property2 ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string'), getSandboxPropertyId('quantity'), ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', references: { [property]: [ 'bar', 'buzz' ], [property2]: 123 } }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', references: { [property]: [ 'bar', 'buzz' ], [property2]: 456 }, reconciliation: { mode: 'merge', matchingReferences: [ property ] } }) res2.claim.id.should.equal(res.claim.id) simplify.references(res2.claim.references).should.deepEqual([ { [property]: [ 'bar', 'buzz' ], [property2]: [ 123, 456 ] }, ]) const res3 = await wbEdit.claim.create({ id, property, value: 'foo', references: { [property]: [ 'bar', 'buzz', 'bla' ], [property2]: 789 }, reconciliation: { mode: 'merge', matchingReferences: [ property ] } }) res3.claim.id.should.equal(res.claim.id) simplify.references(res3.claim.references).should.deepEqual([ { [property]: [ 'bar', 'buzz' ], [property2]: [ 123, 456 ] }, { [property]: [ 'bar', 'buzz', 'bla' ], [property2]: [ 789 ] }, ]) }) it('should match on any specified reference properties when requested', async () => { const [ id, property, property2 ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string'), getSandboxPropertyId('quantity'), ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', references: { [property]: [ 'bar', 'buzz' ], [property2]: 123, } }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', references: { [property]: [ 'bar' ], [property2]: 123, }, reconciliation: { mode: 'merge', matchingReferences: [ `${property}:any` ] } }) res2.claim.id.should.equal(res.claim.id) simplify.references(res2.claim.references).should.deepEqual([ { [property]: [ 'bar', 'buzz' ], [property2]: [ 123 ] } ]) const res3 = await wbEdit.claim.create({ id, property, value: 'foo', references: { [property]: [ 'bar', 'buzz', 'bla' ], [property2]: 456, }, reconciliation: { mode: 'merge', matchingReferences: [ `${property}:any` ] } }) res3.claim.id.should.equal(res.claim.id) simplify.references(res3.claim.references).should.deepEqual([ { [property]: [ 'bar', 'buzz', 'bla' ], [property2]: [ 123, 456 ] }, ]) }) }) }) wikibase-edit-5.3.0/tests/integration/claim/reconciliation_merge_mode.js000066400000000000000000000162011433026310300265300ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { getSandboxPropertyId, getReservedItemId } = require('tests/integration/utils/sandbox_entities') const { simplify } = require('wikibase-sdk') const { randomString } = require('tests/unit/utils') describe('reconciliation: merge mode', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should add a statement when no statement exists', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', reconciliation: { mode: 'merge', } }) res.claim.mainsnak.datavalue.value.should.equal('foo') }) it('should not re-add an existing statement', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo' }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', reconciliation: { mode: 'merge', } }) res2.claim.id.should.equal(res.claim.id) res2.claim.mainsnak.datavalue.value.should.equal('foo') }) it('should not re-add an existing wikibase-item statement', async () => { const [ id, value, qualifierValue, property ] = await Promise.all([ getReservedItemId(), getReservedItemId(), getReservedItemId(), getSandboxPropertyId('wikibase-item') ]) const res = await wbEdit.claim.create({ id, property, value }) const res2 = await wbEdit.claim.create({ id, property, value, qualifiers: { [property]: qualifierValue }, reconciliation: { mode: 'merge', } }) res2.claim.id.should.equal(res.claim.id) res2.claim.mainsnak.datavalue.value.id.should.equal(value) res2.claim.qualifiers[property][0].datavalue.value.id.should.equal(qualifierValue) }) it('should not re-add an existing monolingual text statement', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('monolingualtext') ]) const value = { text: randomString(), language: 'fr' } const qualifierValue = { text: randomString(), language: 'de' } const res = await wbEdit.claim.create({ id, property, value }) const res2 = await wbEdit.claim.create({ id, property, value, qualifiers: { [property]: qualifierValue }, reconciliation: { mode: 'merge', } }) res2.claim.id.should.equal(res.claim.id) res2.claim.mainsnak.datavalue.value.text.should.equal(value.text) res2.claim.mainsnak.datavalue.value.language.should.equal(value.language) res2.claim.qualifiers[property][0].datavalue.value.text.should.equal(qualifierValue.text) res2.claim.qualifiers[property][0].datavalue.value.language.should.equal(qualifierValue.language) }) it('should re-add a statement if expected qualifiers do not match', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: 'bar' } }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: 'buzz' }, reconciliation: { mode: 'merge', matchingQualifiers: [ property ], } }) res2.claim.id.should.not.equal(res.claim.id) }) it('should merge qualifiers', async () => { const [ id, property, property2 ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string'), getSandboxPropertyId('quantity'), ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: 'bar', [property2]: 123, } }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: 'buzz', [property2]: 123, }, reconciliation: { mode: 'merge' } }) res2.claim.id.should.equal(res.claim.id) res2.claim.qualifiers[property].map(simplify.qualifier).should.deepEqual([ 'bar', 'buzz' ]) res2.claim.qualifiers[property2].map(simplify.qualifier).should.deepEqual([ 123 ]) }) it('should not add an identical reference', async () => { const [ id, property, property2 ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string'), getSandboxPropertyId('quantity'), ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', references: [ { [property]: 'bar', [property2]: 123 }, ] }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', references: [ { [property]: 'bar', [property2]: 123 }, { [property]: 'bar' }, ], reconciliation: { mode: 'merge' } }) res2.claim.id.should.equal(res.claim.id) simplify.references(res2.claim.references).should.deepEqual([ { [property2]: [ 123 ], [property]: [ 'bar' ] }, { [property]: [ 'bar' ] }, ]) }) it('should merge matching references', async () => { const [ id, property, property2, property3 ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string'), getSandboxPropertyId('quantity'), getSandboxPropertyId('wikibase-item'), ]) await wbEdit.claim.create({ id, property, value: 'foo', references: [ { [property]: 'bar' }, { [property2]: 456 }, ] }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', references: [ { [property]: 'bar', [property2]: 123 }, { [property2]: 456, [property3]: id }, ], reconciliation: { mode: 'merge', matchingReferences: [ property ], } }) simplify.references(res2.claim.references).should.deepEqual([ { [property2]: [ 123 ], [property]: [ 'bar' ] }, { [property2]: [ 456 ] }, { [property2]: [ 456 ], [property3]: [ id ] }, ]) }) it('should add a different reference', async () => { const [ id, property, property2 ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string'), getSandboxPropertyId('quantity'), ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', references: { [property]: 'bar', [property2]: 123 } }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', references: { [property]: 'bar', [property2]: 124 }, reconciliation: { mode: 'merge' } }) res2.claim.id.should.equal(res.claim.id) simplify.references(res2.claim.references).should.deepEqual([ { [property]: [ 'bar' ], [property2]: [ 123 ] }, { [property]: [ 'bar' ], [property2]: [ 124 ] }, ]) }) }) wikibase-edit-5.3.0/tests/integration/claim/reconciliation_per_datatypes.js000066400000000000000000000247121433026310300272770ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { getSandboxPropertyId, getReservedItemId } = require('tests/integration/utils/sandbox_entities') const { simplify } = require('wikibase-sdk') describe('reconciliation: per datatypes', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) describe('string', () => { it('should support string statements', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'foo', qualifiers: { [property]: 'buzz' } }, { value: 'bar', qualifiers: { [property]: 'bla' } }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'foo', qualifiers: { [property]: 'blo' } }, { value: 'bli', qualifiers: { [property]: 'bla' } }, ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims, { keepQualifiers: true }).should.deepEqual({ [property]: [ { value: 'foo', qualifiers: { [property]: [ 'buzz', 'blo' ] } }, { value: 'bar', qualifiers: { [property]: [ 'bla' ] } }, { value: 'bli', qualifiers: { [property]: [ 'bla' ] } }, ] }) }) }) describe('quantity', () => { it('should support quantity statements', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('quantity') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 123, qualifiers: { [property]: 456 } }, { value: 789, qualifiers: { [property]: 321 } }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 123, qualifiers: { [property]: 987 } }, { value: 654, qualifiers: { [property]: 321 } }, ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims, { keepQualifiers: true }).should.deepEqual({ [property]: [ { value: 123, qualifiers: { [property]: [ 456, 987 ] } }, { value: 789, qualifiers: { [property]: [ 321 ] } }, { value: 654, qualifiers: { [property]: [ 321 ] } }, ] }) }) it('should ignore unspecified parameters', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('quantity') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { amount: 258.82, unit: 'Q712226' }, { amount: '+258', lowerBound: '+256', upperBound: '+259' }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ 258.82, '+258', ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims, { keepRichValues: true }).should.deepEqual({ [property]: [ { amount: 258.82, unit: 'Q712226' }, { amount: 258, lowerBound: 256, upperBound: 259, unit: '1' }, ] }) }) it('should not ignore specified parameters', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('quantity') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { amount: 258.82, unit: 'Q712226' }, { amount: '+258', lowerBound: '+256', upperBound: '+259' }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { amount: 258.82, unit: 'Q712227' }, { amount: '+258', lowerBound: '+255', upperBound: '+259' }, ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims, { keepRichValues: true }).should.deepEqual({ [property]: [ { amount: 258.82, unit: 'Q712226' }, { amount: 258, lowerBound: 256, upperBound: 259, unit: '1' }, { amount: 258.82, unit: 'Q712227' }, { amount: 258, lowerBound: 255, upperBound: 259, unit: '1' }, ] }) }) }) describe('globe-coordinate', () => { it('should support globe-coordinate statements', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('globe-coordinate') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: coordObj(1, 23), qualifiers: { [property]: [ coordObj(4, 56) ] } }, { value: coordObj(7, 89), qualifiers: { [property]: [ coordObj(3, 21) ] } }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { value: coordObj(1, 23), qualifiers: { [property]: [ coordObj(9, 87) ] } }, { value: coordObj(6, 54), qualifiers: { [property]: [ coordObj(3, 21) ] } }, ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims, { keepQualifiers: true, keepRichValues: true }).should.deepEqual({ [property]: [ { value: coordObj(1, 23), qualifiers: { [property]: [ coordObj(4, 56), coordObj(9, 87) ] } }, { value: coordObj(7, 89), qualifiers: { [property]: [ coordObj(3, 21) ] } }, { value: coordObj(6, 54), qualifiers: { [property]: [ coordObj(3, 21) ] } }, ] }) }) }) describe('monolingualtext', () => { it('should support monolingualtext statements', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('monolingualtext') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: { text: 'a', language: 'en' }, qualifiers: { [property]: [ { text: 'z', language: 'en' } ] } }, { value: { text: 'b', language: 'nl' }, qualifiers: { [property]: [ { text: 'y', language: 'de' } ] } }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { value: { text: 'a', language: 'en' }, qualifiers: { [property]: [ { text: 'x', language: 'en' } ] } }, { value: { text: 'b', language: 'fr' }, qualifiers: { [property]: [ { text: 'y', language: 'de' } ] } }, ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims, { keepQualifiers: true, keepRichValues: true }).should.deepEqual({ [property]: [ { value: { text: 'a', language: 'en' }, qualifiers: { [property]: [ { text: 'z', language: 'en' }, { text: 'x', language: 'en' } ] } }, { value: { text: 'b', language: 'nl' }, qualifiers: { [property]: [ { text: 'y', language: 'de' } ] } }, { value: { text: 'b', language: 'fr' }, qualifiers: { [property]: [ { text: 'y', language: 'de' } ] } }, ] }) }) }) describe('time', () => { it('should support time statements', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('time') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: '2013', qualifiers: { [property]: '2015-02' } }, { value: '1936', qualifiers: { [property]: '2011' } }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { value: '2013', qualifiers: { [property]: '1789-08-04' } }, { value: '2018-12-05', qualifiers: { [property]: '2011' } }, ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims, { keepQualifiers: true, timeConverter: 'simple-day' }).should.deepEqual({ [property]: [ { value: '2013', qualifiers: { [property]: [ '2015-02', '1789-08-04' ] } }, { value: '1936', qualifiers: { [property]: [ '2011' ] } }, { value: '2018-12-05', qualifiers: { [property]: [ '2011' ] } }, ] }) }) }) describe('url', () => { it('should ignore trailing slashes', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('url') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'https://www.wikidata.org/' }, { value: 'https://wikiba.se' }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'https://www.wikidata.org' }, { value: 'https://wikiba.se/' }, ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims).should.deepEqual({ [property]: [ 'https://www.wikidata.org/', 'https://wikiba.se' ], }) }) it('should ignore the presence or absence of www subdomain', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('url') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'https://www.wikidata.org' }, { value: 'https://wikiba.se' }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'https://wikidata.org' }, { value: 'https://www.wikiba.se' }, ] }, reconciliation: { mode: 'merge', } }) simplify.claims(res2.entity.claims).should.deepEqual({ [property]: [ 'https://www.wikidata.org', 'https://wikiba.se' ], }) }) }) }) const earth = 'http://www.wikidata.org/entity/Q2' const coordObj = (latitude, longitude) => ({ latitude, longitude, precision: 1 / 3600, globe: earth, altitude: null }) wikibase-edit-5.3.0/tests/integration/claim/reconciliation_remove.js000066400000000000000000000042161433026310300257250ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { getSandboxPropertyId, getReservedItemId } = require('tests/integration/utils/sandbox_entities') const { simplify } = require('wikibase-sdk') const { shouldNotBeCalled } = require('../utils/utils') describe('reconciliation:remove claims', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should remove matching claims', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'foo' }, { value: 'foo' }, { value: 'bar', qualifiers: { [property]: [ 'buzz' ] } }, { value: 'bar', qualifiers: { [property]: [ 'bla' ] } }, ] } }) const res2 = await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'foo', remove: true, reconciliation: {} }, { value: 'bar', qualifiers: { [property]: [ 'bla' ] }, remove: true, reconciliation: { matchingQualifiers: [ property ] } }, ] }, }) simplify.claims(res2.entity.claims, { keepQualifiers: true }).should.deepEqual({ [property]: [ { value: 'bar', qualifiers: { [property]: [ 'buzz' ] } }, ] }) }) it('should reject matching several times the same claim', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'foo' }, ] } }) await wbEdit.entity.edit({ id, claims: { [property]: [ { value: 'foo', remove: true, reconciliation: {} }, { value: 'foo', remove: true, reconciliation: {} }, ] }, }) .then(shouldNotBeCalled) .catch(err => { err.message.should.equal('can not match several times the same claim') }) }) }) wikibase-edit-5.3.0/tests/integration/claim/reconciliation_skip_on_any_value.js000066400000000000000000000025331433026310300301350ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { getSandboxPropertyId, getReservedItemId } = require('tests/integration/utils/sandbox_entities') describe('reconciliation: skip-on-any-value mode', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should add a statement when no statement exists for that property', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', reconciliation: { mode: 'skip-on-any-value', } }) res.claim.mainsnak.datavalue.value.should.equal('foo') }) it('should not add a statement when a statement exists for that property', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo' }) const res2 = await wbEdit.claim.create({ id, property, value: 'bar', reconciliation: { mode: 'skip-on-any-value', } }) res2.claim.id.should.equal(res.claim.id) res2.claim.mainsnak.datavalue.value.should.equal('foo') }) }) wikibase-edit-5.3.0/tests/integration/claim/reconciliation_skip_on_value_match_mode.js000066400000000000000000000043611433026310300314470ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { getSandboxPropertyId, getReservedItemId } = require('tests/integration/utils/sandbox_entities') const { simplify } = require('wikibase-sdk') describe('reconciliation: skip-on-value-match mode', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should add a statement when no statement exists', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', reconciliation: { mode: 'skip-on-value-match', } }) res.claim.mainsnak.datavalue.value.should.equal('foo') }) it('should not re-add an existing statement', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo' }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', reconciliation: { mode: 'skip-on-value-match', } }) res2.claim.id.should.equal(res.claim.id) res2.claim.mainsnak.datavalue.value.should.equal('foo') }) it('should not merge qualifiers and references', async () => { const [ id, property ] = await Promise.all([ getReservedItemId(), getSandboxPropertyId('string') ]) const res = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: 'bar' }, references: { [property]: 'buzz' }, }) const res2 = await wbEdit.claim.create({ id, property, value: 'foo', qualifiers: { [property]: 'bla' }, references: { [property]: 'blu' }, reconciliation: { mode: 'skip-on-value-match', } }) res2.claim.id.should.equal(res.claim.id) res2.claim.mainsnak.datavalue.value.should.equal('foo') simplify.propertyQualifiers(res2.claim.qualifiers[property]).should.deepEqual([ 'bar' ]) simplify.references(res2.claim.references).should.deepEqual([ { [property]: [ 'buzz' ] } ]) }) }) wikibase-edit-5.3.0/tests/integration/claim/remove.js000066400000000000000000000107001433026310300226360ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const { getSandboxPropertyId } = require('tests/integration/utils/sandbox_entities') const { addClaim } = require('tests/integration/utils/sandbox_snaks') const { shouldNotBeCalled } = require('../utils/utils') describe('claim create', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should remove a claim', async () => { const { guid } = await addClaim({ datatype: 'string', value: randomString() }) const res = await wbEdit.claim.remove({ guid }) res.success.should.equal(1) res.claims.should.deepEqual([ guid ]) }) it('should remove several claims on a same entity', async () => { const propertyId = await getSandboxPropertyId('string') const claims = {} claims[propertyId] = [ randomString(), randomString() ] const res = await createEntity(claims) const guids = getGuids(res.entity, propertyId) const res2 = await wbEdit.claim.remove({ guid: guids }) res2.success.should.equal(1) res2.claims.should.deepEqual(guids) }) it('should remove a claim by matching value', async () => { const { guid, id, property, claim } = await addClaim({ datatype: 'string', value: randomString() }) const value = claim.mainsnak.datavalue.value const res = await wbEdit.claim.remove({ id, property, value }) res.success.should.equal(1) res.claims.should.deepEqual([ guid ]) }) it('should remove a claim with qualifiers by matching mainsnak value', async () => { const somePropertyId = await getSandboxPropertyId('string') const value = randomString() const qualifierValue = randomString() const { guid, id, property } = await addClaim({ datatype: 'string', value, qualifiers: { [somePropertyId]: qualifierValue, }, }) const res = await wbEdit.claim.remove({ id, property, value, qualifiers: { [somePropertyId]: qualifierValue }, }) res.success.should.equal(1) res.claims.should.deepEqual([ guid ]) }) it('should remove a claim with qualifiers by matching mainsnak value', async () => { const somePropertyId = await getSandboxPropertyId('string') const value = randomString() const qualifierValue = randomString() const { guid, id, property } = await addClaim({ datatype: 'string', value, qualifiers: { [somePropertyId]: qualifierValue, }, }) const res = await wbEdit.claim.remove({ id, property, value, qualifiers: { [somePropertyId]: qualifierValue }, reconciliation: { matchingQualifiers: [ somePropertyId ] } }) res.success.should.equal(1) res.claims.should.deepEqual([ guid ]) }) it('should refuse to remove a claim with non matching qualifiers', async () => { const somePropertyId = await getSandboxPropertyId('string') const value = randomString() const { id, property } = await addClaim({ datatype: 'string', value, qualifiers: { [somePropertyId]: randomString(), }, }) await wbEdit.claim.remove({ id, property, value, qualifiers: { [somePropertyId]: randomString() }, reconciliation: { matchingQualifiers: [ somePropertyId ] } }) .then(shouldNotBeCalled) .catch(err => { err.message.should.equal('claim not found') }) }) // The documentation explicitly specify that the claims should belong to the same entity // https://www.wikidata.org/w/api.php?action=help&modules=wbremoveclaims // it('should remove several claims on different entities', async () => { // const propertyId = await getSandboxPropertyId('string') // const claims = {} // claims[propertyId] = randomString() // const [ res1, res2 ] = await Promise.all([ // createEntity(claims), // createEntity(claims) // ]) // const guid1 = getGuids(res1.entity, propertyId)[0] // const guid2 = getGuids(res2.entity, propertyId)[0] // const res2 = await wbEdit.claim.remove({ guid: [ guid1, guid2 ] }) // res2.success.should.equal(1) // res2.claims.should.deepEqual(guids) // }) }) const createEntity = claims => { return wbEdit.entity.create({ labels: { en: randomString() }, claims }) } const getGuids = (entity, propertyId) => entity.claims[propertyId].map(claim => claim.id) wikibase-edit-5.3.0/tests/integration/claim/set.js000066400000000000000000000012361433026310300221400ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const { getSandboxClaim } = require('tests/integration/utils/sandbox_entities') describe('claim set', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should set a claim', async () => { const claim = await getSandboxClaim() const { property } = claim.mainsnak const value = randomString() const res = await wbEdit.claim.set({ guid: claim.id, property, value }) res.claim.mainsnak.datavalue.value.should.equal(value) }) }) wikibase-edit-5.3.0/tests/integration/claim/update.js000066400000000000000000000210501433026310300226230ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const updateClaim = wbEdit.claim.update const editEntity = wbEdit.entity.edit const { shouldNotBeCalled } = require('tests/integration/utils/utils') const { getSandboxItemId, getSandboxPropertyId, getReservedItemId } = require('tests/integration/utils/sandbox_entities') const { addClaim } = require('tests/integration/utils/sandbox_snaks') const { randomString, randomNumber } = require('tests/unit/utils') const { simplify } = require('wikibase-sdk') describe('claim update', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) describe('find a claim from an item id, a property, and an old value', () => { it('should update a string claim', async () => { const oldValue = randomString() const newValue = randomString() const { id, property, guid } = await addClaim({ datatype: 'string', value: oldValue }) const res = await updateClaim({ id, property, oldValue, newValue }) res.claim.id.should.equal(guid) simplify.claim(res.claim).should.equal(newValue) }) it('should fetch the properties it needs', async () => { const oldValue = randomString() const newValue = randomString() const { id } = await addClaim({ datatype: 'string', value: oldValue }) try { await updateClaim({ id, property: 'P999999', oldValue, newValue }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('property not found') } }) it('should update a wikibase-item claim', async () => { const [ oldValue, newValue ] = await Promise.all([ getSandboxItemId(), getReservedItemId() ]) const { id, guid, property } = await addClaim({ datatype: 'wikibase-item', value: oldValue }) const res = await updateClaim({ id, property, oldValue, newValue }) res.claim.id.should.equal(guid) simplify.claim(res.claim).should.deepEqual(newValue) }) it('should reject if old value is missing', async () => { const oldValue = randomString() const newValue = randomString() const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('string') ]) try { await updateClaim({ id, property, oldValue, newValue }).then(shouldNotBeCalled) } catch (err) { // Accept both messages as the sandbox item might not have pre-existing claims for that property const possibleMessages = [ 'no property claims found', 'claim not found' ] possibleMessages.includes(err.message).should.be.true() } }) it('should reject claim updates from values when several claims match', async () => { const oldValue = randomString() const newValue = randomString() const [ res1 ] = await Promise.all([ addClaim({ datatype: 'string', value: oldValue }), addClaim({ datatype: 'string', value: oldValue }) ]) const { id, property } = res1 try { await updateClaim({ id, property, oldValue, newValue }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('snak not found: too many matching snaks') } }) }) describe('find a claim from a guid', () => { it('should update a claim', async () => { const oldValue = randomString() const newValue = randomString() const { guid, property } = await addClaim({ datatype: 'string', value: oldValue }) const res = await updateClaim({ guid, property, newValue }) res.claim.id.should.equal(guid) res.claim.mainsnak.datavalue.value.should.equal(newValue) }) }) describe('common', () => { it('should keep the references and qualifiers', async () => { const oldValue = randomString() const newValue = randomString() const qualifierValue = randomString() const referenceValue = randomString() const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('string') ]) const claim = { value: oldValue, qualifiers: {}, references: {} } claim.qualifiers[property] = qualifierValue claim.references[property] = referenceValue const data = { id, claims: {} } data.claims[property] = claim const resA = await editEntity(data) const resAClaim = resA.entity.claims[property].slice(-1)[0] const guid = resAClaim.id const resB = await updateClaim({ id, property, oldValue, newValue }) const simplifiedClaim = simplify.claim(resB.claim, { keepIds: true, keepQualifiers: true, keepReferences: true }) simplifiedClaim.id.should.equal(guid) simplifiedClaim.value.should.equal(newValue) simplifiedClaim.qualifiers[property][0].should.equal(qualifierValue) simplifiedClaim.references[0][property][0].should.equal(referenceValue) }) it('should update a wikibase-item', async () => { const [ oldValue, newValue ] = await Promise.all([ getSandboxItemId(), getReservedItemId() ]) const { guid, property } = await addClaim({ datatype: 'wikibase-item', value: oldValue }) const res = await updateClaim({ guid, property, newValue }) res.claim.id.should.equal(guid) simplify.claim(res.claim).should.deepEqual(newValue) }) it('should update a monolingual text claim', async () => { const oldValue = { text: randomString(), language: 'fr' } const newValue = { text: randomString(), language: 'de' } const { guid, property } = await addClaim({ datatype: 'monolingualtext', value: oldValue }) const res = await updateClaim({ guid, property, newValue }) res.claim.id.should.equal(guid) simplify.claim(res.claim, { keepRichValues: true }).should.deepEqual(newValue) }) it('should update a quantity claim with a unit', async () => { const oldValue = { amount: randomNumber(), unit: 'Q1' } const newValue = { amount: randomNumber(), unit: 'Q2' } const { guid, property } = await addClaim({ datatype: 'quantity', value: oldValue }) const res = await updateClaim({ guid, property, newValue }) res.claim.id.should.equal(guid) simplify.claim(res.claim, { keepRichValues: true }).should.deepEqual(newValue) }) it('should update a time claim', async () => { const oldYear = 1000 + randomNumber(3) const newYear = 1000 + randomNumber(3) const oldValue = `${oldYear}-02-26` const newValue = `${newYear}-10-25` const { guid, property } = await addClaim({ datatype: 'time', value: oldValue }) const res = await updateClaim({ guid, property, newValue }) res.claim.id.should.equal(guid) simplify.claim(res.claim).split('T')[0].should.equal(newValue) }) it('should update a globe-coordinate claim', async () => { const oldValue = { latitude: randomNumber(2), longitude: randomNumber(2), precision: 0.01, globe: 'http://www.wikidata.org/entity/Q111' } const newValue = { latitude: randomNumber(2), longitude: randomNumber(2), precision: 0.01, globe: 'http://www.wikidata.org/entity/Q112' } const { guid, property } = await addClaim({ datatype: 'globe-coordinate', value: oldValue }) const res = await updateClaim({ guid, property, newValue }) res.claim.id.should.equal(guid) const { value } = res.claim.mainsnak.datavalue value.latitude.should.equal(newValue.latitude) value.longitude.should.equal(newValue.longitude) value.precision.should.equal(newValue.precision) value.globe.should.equal(newValue.globe) }) it('should update a claim rank', async () => { const oldValue = randomString() const newValue = randomString() const { guid, property } = await addClaim({ datatype: 'string', value: oldValue }) const res = await updateClaim({ guid, property, newValue, rank: 'preferred' }) res.claim.rank.should.equal('preferred') }) it('should be able to only update a claim rank', async () => { const oldValue = randomString() const { guid } = await addClaim({ datatype: 'string', value: oldValue }) const res = await updateClaim({ guid, rank: 'preferred' }) res.claim.rank.should.equal('preferred') }) it('should update a claim snaktype', async () => { const oldValue = randomString() const newValue = { snaktype: 'novalue' } const { guid, property } = await addClaim({ datatype: 'string', value: oldValue }) const res = await updateClaim({ guid, property, newValue }) res.claim.mainsnak.snaktype.should.equal('novalue') }) }) }) wikibase-edit-5.3.0/tests/integration/credentials.js000066400000000000000000000054051433026310300225570ustar00rootroot00000000000000require('should') const { instance, credentials } = require('config') const WBEdit = require('root') const { randomString } = require('tests/unit/utils') const { undesiredRes, shouldNotBeCalled, rethrowShouldNotBeCalledErrors } = require('./utils/utils') const params = () => ({ labels: { en: randomString() } }) describe('credentials', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should accept config at initialization', async () => { const wbEdit = WBEdit({ instance, credentials }) const res = await wbEdit.entity.create(params()) res.success.should.equal(1) }) it('should accept credentials at request time', async () => { const wbEdit = WBEdit({ instance }) const res = await wbEdit.entity.create(params(), { credentials }) res.success.should.equal(1) }) it('should accept instance at request time', async () => { const wbEdit = WBEdit() const res = await wbEdit.entity.create(params(), { instance, credentials }) res.success.should.equal(1) }) it('should reject undefined credentials', async () => { const creds = { username: null, password: null } const wbEdit = WBEdit({ instance, credentials: creds }) try { await wbEdit.entity.create(params()).then(shouldNotBeCalled) } catch (err) { rethrowShouldNotBeCalledErrors(err) err.message.should.equal('missing credentials') } }) it('should allow defining credentials both at initialization and request time', async () => { const wbEdit = WBEdit({ credentials }) const res = await wbEdit.entity.create(params(), { instance, credentials }) res.success.should.equal(1) }) it('should reject defining both oauth and username:password credentials', async () => { const creds = { username: 'abc', password: 'def', oauth: {} } const wbEdit = WBEdit({ instance, credentials: creds }) try { await wbEdit.entity.create(params()).then(shouldNotBeCalled) } catch (err) { rethrowShouldNotBeCalledErrors(err) err.message.should.equal('credentials can not be both oauth tokens, and a username and password') } }) // TODO: run a similar test for oauth if (!('oauth' in credentials)) { it('should re-generate credentials when re-using a pre-existing credentials object', done => { const wbEdit = WBEdit({ instance }) const creds = Object.assign({}, credentials) wbEdit.entity.create(params(), { credentials: creds }) .then(() => { creds.username = 'foo' return wbEdit.entity.create(params(), { credentials: creds }) }) .then(undesiredRes(done)) .catch(err => { err.body.error.code.should.equal('assertuserfailed') done() }) .catch(done) }) } }) wikibase-edit-5.3.0/tests/integration/description/000077500000000000000000000000001433026310300222435ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/description/set.js000066400000000000000000000031711433026310300233760ustar00rootroot00000000000000const should = require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const { getSandboxItemId, getRefreshedEntity } = require('tests/integration/utils/sandbox_entities') const language = 'fr' describe('description set', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should set a description', async () => { const id = await getSandboxItemId() const value = `Bac à Sable (${randomString()})` const res = await wbEdit.description.set({ id, language, value }) res.success.should.equal(1) const item = await getRefreshedEntity(id) item.descriptions[language].value.should.equal(value) }) it('should remove a description when passed value=null', async () => { const id = await getSandboxItemId() const value = `Bac à Sable (${randomString()})` await wbEdit.description.set({ id, language, value }) const res = await wbEdit.description.set({ id, language, value: null }) res.success.should.equal(1) const item = await getRefreshedEntity(id) should(item.descriptions[language]).not.be.ok() }) it('should remove a description when passed value=""', async () => { const id = await getSandboxItemId() const value = `Bac à Sable (${randomString()})` await wbEdit.description.set({ id, language, value }) const res = await wbEdit.description.set({ id, language, value: '' }) res.success.should.equal(1) const item = await getRefreshedEntity(id) should(item.descriptions[language]).not.be.ok() }) }) wikibase-edit-5.3.0/tests/integration/entity/000077500000000000000000000000001433026310300212345ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/entity/create.js000066400000000000000000000024701433026310300230400ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const { getSandboxPropertyId } = require('tests/integration/utils/sandbox_entities') describe('entity create', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should create a property', async () => { const res = await wbEdit.entity.create({ type: 'property', datatype: 'external-id', labels: { en: randomString() } }) res.success.should.equal(1) res.entity.type.should.equal('property') }) it('should create an item', async () => { const [ pidA, pidB, pidC ] = await Promise.all([ getSandboxPropertyId('string'), getSandboxPropertyId('external-id'), getSandboxPropertyId('url') ]) const claims = {} claims[pidA] = { value: randomString(), qualifiers: {}, references: {} } claims[pidA].qualifiers[pidB] = randomString() claims[pidA].references[pidC] = 'http://foo.bar' const res = await wbEdit.entity.create({ type: 'item', labels: { en: randomString() }, descriptions: { en: randomString() }, aliases: { en: randomString() }, claims }) res.success.should.equal(1) }) }) wikibase-edit-5.3.0/tests/integration/entity/delete.js000066400000000000000000000020751433026310300230400ustar00rootroot00000000000000require('should') const config = require('config') const { instance, credentialsAlt } = config // Use credentialsAlt as the OAuth token might miss the permission to delete pages // thus getting a 'permissiondenied' error const wbEdit = require('root')({ instance, credentials: credentialsAlt }) const { randomString } = require('tests/unit/utils') describe('entity delete', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should delete an item', async () => { const resA = await wbEdit.entity.create({ labels: { en: randomString() } }) const { id } = resA.entity const resB = await wbEdit.entity.delete({ id }) resB.delete.title.should.endWith(id) }) it('should delete a property', async () => { const resA = await wbEdit.entity.create({ type: 'property', datatype: 'string', labels: { en: randomString() } }) const { id } = resA.entity const resB = await wbEdit.entity.delete({ id }) resB.delete.title.should.equal(`Property:${id}`) }) }) wikibase-edit-5.3.0/tests/integration/entity/edit.js000066400000000000000000000131141433026310300225170ustar00rootroot00000000000000const should = require('should') const config = require('config') const wbEdit = require('root')(config) const { simplify } = require('wikibase-sdk') const { randomString } = require('tests/unit/utils') const { getSandboxItemId, getSandboxPropertyId, createItem } = require('tests/integration/utils/sandbox_entities') const { addClaim } = require('tests/integration/utils/sandbox_snaks') const { getEntity } = require('../utils/utils') const getProperty = require('tests/integration/utils/get_property') describe('entity edit', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should edit an item', async () => { const label = randomString() const id = await getSandboxItemId() const res = await wbEdit.entity.edit({ id, labels: { nl: label } }) res.entity.labels.nl.value.should.equal(label) }) it('should clear and edit an item', async () => { const [ pidA, pidB, pidC ] = await Promise.all([ getSandboxPropertyId('string'), getSandboxPropertyId('external-id'), getSandboxPropertyId('url') ]) const claims = {} claims[pidA] = { value: randomString(), qualifiers: {}, references: {} } claims[pidA].qualifiers[pidB] = randomString() claims[pidA].references[pidC] = 'http://foo.bar' const params = { labels: { en: randomString() }, descriptions: { en: randomString() }, aliases: { en: randomString() }, claims } const resA = await wbEdit.entity.create(params) const newLabel = randomString() const resB = await wbEdit.entity.edit({ id: resA.entity.id, clear: true, labels: { en: newLabel } }) resB.success.should.equal(1) const { entity } = resB entity.labels.should.deepEqual({ en: { language: 'en', value: newLabel } }) entity.descriptions.should.deepEqual({}) entity.aliases.should.deepEqual({}) entity.claims.should.deepEqual({}) }) it('should set an item claim rank', async () => { const [ id, propertyId ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('string') ]) const claims = {} claims[propertyId] = [ { rank: 'preferred', value: 'foo' }, { rank: 'normal', value: 'bar' }, { rank: 'deprecated', value: 'buzz' } ] const res = await wbEdit.entity.edit({ id, claims }) const propertyClaims = res.entity.claims[propertyId].slice(-3) const simplifiedPropertyClaims = simplify.propertyClaims(propertyClaims, { keepRanks: true, keepNonTruthy: true }) simplifiedPropertyClaims.should.deepEqual(claims[propertyId]) }) it('should clear language terms by passing null', async () => { const resA = await wbEdit.entity.create({ labels: { en: randomString() }, descriptions: { en: randomString() }, aliases: { en: randomString() }, }) const resB = await wbEdit.entity.edit({ id: resA.entity.id, labels: { en: null }, descriptions: { en: null }, aliases: { en: null }, }) resB.success.should.equal(1) }) it('should add an alias without removing the previous aliases', async () => { const aliasA = randomString() const aliasB = randomString() const { entity } = await wbEdit.entity.create({ labels: { en: randomString() }, aliases: { en: aliasA }, }) const resB = await wbEdit.entity.edit({ id: entity.id, aliases: { en: { value: aliasB, add: true } }, }) simplify.aliases(resB.entity.aliases).en.should.deepEqual([ aliasA, aliasB ]) }) // This test requires setting an instance with sitelinks // (such as test.wikidata.org) in config, and is thus disabled by default xit('should add and remove a sitelink', async () => { const id = await getSandboxItemId() const yearArticleTitle = Math.trunc(Math.random() * 2020).toString() const res = await wbEdit.entity.edit({ id, sitelinks: { frwiki: yearArticleTitle, dewiki: { title: yearArticleTitle, badges: [ 'Q608', 'Q609' ] } } }) res.entity.sitelinks.frwiki.title.should.equal(yearArticleTitle) res.entity.sitelinks.dewiki.title.should.equal(yearArticleTitle) const res2 = await wbEdit.entity.edit({ id, sitelinks: { frwiki: { title: yearArticleTitle, remove: true }, dewiki: null } }) should(res2.entity.sitelinks.frwiki).not.be.ok() should(res2.entity.sitelinks.dewiki).not.be.ok() }) describe('raw mode', () => { it('shoud accept raw data', async () => { const [ labelA, labelB, claimValueA, claimValueB ] = [ randomString(), randomString(), randomString(), randomString() ] const { id } = await createItem({ labels: { en: labelA } }) const { property } = await addClaim({ id, datatype: 'string', value: claimValueA }) const { id: otherStringPropertyId } = await getProperty({ datatype: 'string', reserved: true }) const { labels, claims } = await getEntity(id) labels.en.value = labelB const claim = claims[property][0] const { id: guid } = claim delete claim.id const removedClaim = { id: guid, remove: true } claim.mainsnak.property = otherStringPropertyId claim.mainsnak.datavalue.value = claimValueB const res = await wbEdit.entity.edit({ rawMode: true, id, labels, claims: [ removedClaim, claim ] }) res.entity.labels.en.value.should.equal(labelB) should(res.entity.claims[property]).not.be.ok() res.entity.claims[otherStringPropertyId][0].mainsnak.datavalue.value.should.equal(claimValueB) }) }) }) wikibase-edit-5.3.0/tests/integration/entity/merge.js000066400000000000000000000016201433026310300226700ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') describe('entity merge', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) // Do not run this test on the local instance as it currently fails // https://phabricator.wikimedia.org/T232925 xit('should merge two items', async () => { const [ res1, res2 ] = await Promise.all([ wbEdit.entity.create({ labels: { en: randomString() } }), wbEdit.entity.create({ labels: { en: randomString() } }) ]) const { id: from } = res1.entity const { id: to } = res2.entity const res3 = await wbEdit.entity.merge({ from, to }) res3.success.should.equal(1) res3.redirected.should.equal(1) res3.from.id.should.equal(from) res3.to.id.should.equal(to) }) }) wikibase-edit-5.3.0/tests/integration/fetch_properties_datatypes.js000066400000000000000000000020121433026310300256740ustar00rootroot00000000000000require('should') const config = require('config') const { undesiredRes } = require('./utils/utils') const { getSandboxPropertyId } = require('tests/integration/utils/sandbox_entities') const fetchPropertiesDatatypes = require('lib/properties/fetch_properties_datatypes') describe('fetch properties datatypes', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should fetch a property datatype', async () => { const propertyId = await getSandboxPropertyId('wikibase-item') await fetchPropertiesDatatypes(config, [ propertyId ]) config.properties.should.be.an.Object() const datatype = config.properties[propertyId] datatype.should.equal('wikibase-item') }) it("should throw when it can't find the property datatype", done => { fetchPropertiesDatatypes(config, [ 'P999999' ]) .then(undesiredRes(done)) .catch(err => { err.message.should.equal('property not found') done() }) .catch(done) }) }) wikibase-edit-5.3.0/tests/integration/get_auth_data.js000066400000000000000000000035001433026310300230450ustar00rootroot00000000000000const should = require('should') const { instance, credentials, credentialsAlt } = require('config') const { username, password } = credentialsAlt const GetAuthData = require('lib/request/get_auth_data') const validateAndEnrichConfig = require('lib/validate_and_enrich_config') const { isBotPassword } = require('tests/integration/utils/utils') describe('get auth data', function () { this.timeout(10000) it('should get token from username and password', async () => { const config = validateAndEnrichConfig({ instance, credentials: { username, password } }) const getAuthData = GetAuthData(config) getAuthData.should.be.a.Function() const { token, cookie } = await getAuthData() token.length.should.equal(42) if (!isBotPassword(password)) { should(/.+UserID=\d+/.test(cookie)).be.true('should contain user ID') } should(/.+[sS]ession=\w{32}/.test(cookie)).be.true('should contain session ID') }) it('should get token from oauth', async () => { const config = validateAndEnrichConfig({ instance, credentials }) const getAuthData = GetAuthData(config) const { token } = await getAuthData() token.length.should.equal(42) }) it('should return the same data when called before the token expired', async () => { const config = validateAndEnrichConfig({ instance, credentials }) const getAuthData = GetAuthData(config) const dataA = await getAuthData() const dataB = await getAuthData() dataA.should.equal(dataB) dataA.token.should.equal(dataB.token) }) it('should return refresh data if requested', async () => { const config = validateAndEnrichConfig({ instance, credentials }) const getAuthData = GetAuthData(config) const dataA = await getAuthData() const dataB = await getAuthData({ refresh: true }) dataA.should.not.equal(dataB) }) }) wikibase-edit-5.3.0/tests/integration/get_token.js000066400000000000000000000043101433026310300222330ustar00rootroot00000000000000const should = require('should') const { instance, credentials, credentialsAlt } = require('config') const { username, password } = credentialsAlt const { undesiredRes, isBotPassword } = require('tests/integration/utils/utils') const GetToken = require('lib/request/get_token') const validateAndEnrichConfig = require('lib/validate_and_enrich_config') describe('get token', function () { this.timeout(10000) it('should get token from username and password', async () => { const config = validateAndEnrichConfig({ instance, credentials: { username, password } }) const getToken = GetToken(config) getToken.should.be.a.Function() const { cookie, token } = await getToken() token.length.should.be.above(40) if (!isBotPassword(password)) { should(/.+UserID=\d+/.test(cookie)).be.true('should contain user ID') } should(/.+[sS]ession=\w{32}/.test(cookie)).be.true('should contain session ID') }) it('should get token from oauth', async () => { const config = validateAndEnrichConfig({ instance, credentials }) const getToken = GetToken(config) getToken.should.be.a.Function() const { token } = await getToken() token.length.should.be.above(40) }) it('should reject on invalid username/password credentials', done => { const invalidCreds = { username: 'inva', password: 'lid' } const config = validateAndEnrichConfig({ instance, credentials: invalidCreds }) const getToken = GetToken(config) getToken.should.be.a.Function() getToken() .then(undesiredRes(done)) .catch(err => { err.message.should.equal('failed to login: invalid username/password') done() }) .catch(done) }) it('should reject on invalid oauth credentials', done => { const invalidCreds = { oauth: { consumer_key: 'in', consumer_secret: 'va', token: 'li', token_secret: 'd' } } const config = validateAndEnrichConfig({ instance, credentials: { oauth: invalidCreds } }) const getToken = GetToken(config) getToken.should.be.a.Function() getToken() .then(undesiredRes(done)) .catch(err => { err.message.should.endWith('Invalid consumer') done() }) .catch(done) }) }) wikibase-edit-5.3.0/tests/integration/label/000077500000000000000000000000001433026310300207775ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/label/set.js000066400000000000000000000030571433026310300221350ustar00rootroot00000000000000const should = require('should') const config = require('config') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const { getSandboxItemId, getRefreshedEntity } = require('tests/integration/utils/sandbox_entities') const language = 'fr' describe('label set', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should set a label', async () => { const id = await getSandboxItemId() const value = `Bac à Sable (${randomString()})` const res = await wbEdit.label.set({ id, language, value }) res.success.should.equal(1) const item = await getRefreshedEntity(id) item.labels[language].value.should.equal(value) }) it('should remove a label when passed value=null', async () => { const id = await getSandboxItemId() const value = `Bac à Sable (${randomString()})` await wbEdit.label.set({ id, language, value }) const res = await wbEdit.label.set({ id, language, value: '' }) res.success.should.equal(1) const item = await getRefreshedEntity(id) should(item.labels[language]).not.be.ok() }) it('should remove a label when passed value=""', async () => { const id = await getSandboxItemId() const value = `Bac à Sable (${randomString()})` await wbEdit.label.set({ id, language, value }) const res = await wbEdit.label.set({ id, language, value: '' }) res.success.should.equal(1) const item = await getRefreshedEntity(id) should(item.labels[language]).not.be.ok() }) }) wikibase-edit-5.3.0/tests/integration/maxlag.js000066400000000000000000000024771433026310300215410ustar00rootroot00000000000000require('should') const config = require('config') const WBEdit = require('root') const { randomString } = require('tests/unit/utils') const { undesiredRes } = require('./utils/utils') const { getSandboxItemId } = require('tests/integration/utils/sandbox_entities') describe('maxlag', function () { this.timeout(120 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should accept a maxlag from initialization configuration', done => { const customConfig = Object.assign({ maxlag: -100, autoRetry: false }, config) const wbEdit = WBEdit(customConfig) doAction(wbEdit) .then(undesiredRes(done)) .catch(err => { err.body.error.code.should.equal('maxlag') done() }) .catch(done) }) it('should accept a maxlag from request configuration', done => { const customConfig = Object.assign({ maxlag: 100, autoRetry: false }, config) const wbEdit = WBEdit(customConfig) doAction(wbEdit, { maxlag: -100 }) .then(undesiredRes(done)) .catch(err => { err.body.error.code.should.equal('maxlag') done() }) .catch(done) }) }) const doAction = async (wbEdit, reqConfig) => { const id = await getSandboxItemId() const params = { id, language: 'fr', value: randomString() } return wbEdit.alias.add(params, reqConfig) } wikibase-edit-5.3.0/tests/integration/multi_users.js000066400000000000000000000027151433026310300226360ustar00rootroot00000000000000require('should') const { instance, credentialsAlt, secondUserCredentials } = require('config') const { randomString } = require('tests/unit/utils') const WBEdit = require('root') const { getSandboxItemId } = require('tests/integration/utils/sandbox_entities') const { getEntityHistory } = require('./utils/utils') describe('multi users edits', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should allow to change user at each request', async () => { const wbEdit = WBEdit({ instance }) const id = await getSandboxItemId() await addAlias(wbEdit, id, { anonymous: true }) await addAlias(wbEdit, id, { credentials: credentialsAlt }) await addAlias(wbEdit, id, { credentials: secondUserCredentials }) await addAlias(wbEdit, id, { anonymous: true }) await addAlias(wbEdit, id, { credentials: credentialsAlt }) const revisions = await getEntityHistory(id) const addAliasRevisions = revisions.slice(-5) addAliasRevisions[0].anon.should.equal('') addAliasRevisions[1].user.should.equal(credentialsAlt.username) addAliasRevisions[2].user.should.equal(secondUserCredentials.username) addAliasRevisions[3].anon.should.equal('') addAliasRevisions[4].user.should.equal(credentialsAlt.username) }) }) const addAlias = async (wbEdit, id, reqConfig) => { return wbEdit.alias.add({ id, language: 'la', value: randomString() }, reqConfig) } wikibase-edit-5.3.0/tests/integration/qualifier/000077500000000000000000000000001433026310300217015ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/qualifier/move.js000066400000000000000000000200401433026310300232010ustar00rootroot00000000000000const should = require('should') const config = require('config') const wbEdit = require('root')(config) const { move: moveQualifier } = wbEdit.qualifier const { shouldNotBeCalled, getLastEditSummary } = require('tests/integration/utils/utils') const { getSomeGuid } = require('tests/integration/utils/sandbox_entities') const { addClaim, addQualifier } = require('tests/integration/utils/sandbox_snaks') const { randomString } = require('tests/unit/utils') const getProperty = require('tests/integration/utils/get_property') describe('qualifier move', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should reject missing guid', async () => { try { await moveQualifier({}).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('missing claim guid') } }) it('should reject missing old property', async () => { try { const guid = await getSomeGuid() await moveQualifier({ guid }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('missing old property') } }) it('should reject missing new property', async () => { try { const { guid, property: oldProperty } = await addQualifier({ datatype: 'string', value: randomString() }) await moveQualifier({ guid, oldProperty }).then(shouldNotBeCalled) } catch (err) { err.message.should.equal('missing new property') } }) it('should move property qualifiers', async () => { const [ valueA, valueB ] = [ randomString(), randomString() ] const { guid } = await addClaim({ datatype: 'string', value: randomString() }) const { property: oldProperty } = await addQualifier({ guid, datatype: 'string', value: valueA }) await addQualifier({ guid, property: oldProperty, value: valueB }) const { id: newProperty } = await getProperty({ datatype: 'string', reserved: true }) const { claim } = await moveQualifier({ guid, oldProperty, newProperty }) should(claim.qualifiers[oldProperty]).not.be.ok() claim.qualifiers[newProperty][0].datavalue.value.should.equal(valueA) claim.qualifiers[newProperty][1].datavalue.value.should.equal(valueB) }) it('should move a unique qualifier', async () => { const [ valueA, valueB ] = [ randomString(), randomString() ] const { id: oldProperty } = await getProperty({ datatype: 'string', reserved: true }) const { guid, hash } = await addQualifier({ property: oldProperty, value: valueA }) await addQualifier({ guid, property: oldProperty, value: valueB }) const { id: newProperty } = await getProperty({ datatype: 'string', reserved: true }) const { claim } = await moveQualifier({ guid, hash, oldProperty, newProperty }) claim.qualifiers[oldProperty][0].datavalue.value.should.equal(valueB) claim.qualifiers[newProperty][0].datavalue.value.should.equal(valueA) }) it('should generate a custom summary', async () => { const [ valueA, valueB ] = [ randomString(), randomString() ] const { id, guid } = await addClaim({ datatype: 'string', value: randomString() }) const { property: oldProperty } = await addQualifier({ guid, datatype: 'string', value: valueA }) await addQualifier({ guid, property: oldProperty, value: valueB }) const { id: newProperty } = await getProperty({ datatype: 'string', reserved: true }) await moveQualifier({ guid, oldProperty, newProperty }) const summary = await getLastEditSummary(id) summary.split('*/')[1].trim() .should.equal(`moving ${guid} ${oldProperty} qualifiers to ${newProperty}`) }) describe('type conversions', () => { describe('string->quantity', () => { it('should convert a positive string number value to quantity', async () => { await testTypeConversion({ originalType: 'string', originalValue: '765.521521', targetType: 'quantity', targetValue: { amount: '+765.521521', unit: '1' }, }) }) it('should convert a signed positive string number value to quantity', async () => { await testTypeConversion({ originalType: 'string', originalValue: '+123.52', targetType: 'quantity', targetValue: { amount: '+123.52', unit: '1' }, }) }) it('should convert a negative string number value to quantity', async () => { await testTypeConversion({ originalType: 'string', originalValue: '-5519.521521', targetType: 'quantity', targetValue: { amount: '-5519.521521', unit: '1' }, }) }) it('should reject to convert a non-number string', async () => { await testTypeConversion({ originalType: 'string', originalValue: '123.abc', targetType: 'quantity' }) .then(shouldNotBeCalled) .catch(err => { err.message.should.equal("properties datatype don't match and string->quantity type conversion failed: invalid string number") }) }) }) describe('quantity->string', () => { it('should convert a positive integer to a string', async () => { await testTypeConversion({ originalType: 'quantity', originalValue: 96, targetType: 'string', targetValue: '96', }) }) it('should convert a positive float to a string', async () => { await testTypeConversion({ originalType: 'quantity', originalValue: 987.456, targetType: 'string', targetValue: '987.456', }) }) it('should convert a negative integer to a string', async () => { await testTypeConversion({ originalType: 'quantity', originalValue: -654, targetType: 'string', targetValue: '-654', }) }) it('should convert a negative float to a string', async () => { await testTypeConversion({ originalType: 'quantity', originalValue: -12.56, targetType: 'string', targetValue: '-12.56', }) }) }) describe('external-id->string', () => { it('should convert an external-id to a string', async () => { const value = randomString() await testTypeConversion({ originalType: 'external-id', originalValue: value, targetType: 'string', targetValue: value, }) }) }) describe('string->external-id', () => { it('should convert a string to an external-id', async () => { const value = randomString() await testTypeConversion({ originalType: 'string', originalValue: value, targetType: 'external-id', targetValue: value, }) }) }) describe('monolingualtext->string', () => { it('should convert a monolingualtext to a string', async () => { const value = randomString() await testTypeConversion({ originalType: 'monolingualtext', originalValue: { text: value, language: 'en' }, targetType: 'string', targetValue: value, }) }) }) describe('monolingualtext->string', () => { it('should not convert a string to a monolingualtext', async () => { await testTypeConversion({ originalType: 'string', originalValue: randomString(), targetType: 'monolingualtext' }) .then(shouldNotBeCalled) .catch(err => { err.message.should.startWith("properties datatype don't match") }) }) }) }) }) const testTypeConversion = async ({ originalType, originalValue, targetType, targetValue }) => { const { id: oldProperty } = await getProperty({ datatype: originalType }) const { id: newProperty } = await getProperty({ datatype: targetType }) const { guid, hash } = await addQualifier({ property: oldProperty, value: originalValue }) const { claim } = await moveQualifier({ guid, hash, oldProperty, newProperty }) const movedQualifier = claim.qualifiers[newProperty].slice(-1)[0] movedQualifier.datavalue.value.should.deepEqual(targetValue) } wikibase-edit-5.3.0/tests/integration/qualifier/remove.js000066400000000000000000000020731433026310300235360ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const removeQualifier = wbEdit.qualifier.remove const { randomString } = require('tests/unit/utils') const { addQualifier } = require('tests/integration/utils/sandbox_snaks') describe('qualifier remove', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should remove a qualifier', async () => { const { guid, qualifier } = await addQualifier({ datatype: 'string', value: randomString() }) const res = await removeQualifier({ guid, hash: qualifier.hash }) res.success.should.equal(1) }) it('should remove several qualifiers', async () => { const [ res1, res2 ] = await Promise.all([ addQualifier({ datatype: 'string', value: randomString() }), addQualifier({ datatype: 'string', value: randomString() }) ]) const res = await removeQualifier({ guid: res1.guid, hash: [ res1.qualifier.hash, res2.qualifier.hash ] }) res.success.should.equal(1) }) }) wikibase-edit-5.3.0/tests/integration/qualifier/set.js000066400000000000000000000025151433026310300230350ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const setQualifier = wbEdit.qualifier.set const { randomString } = require('tests/unit/utils') const { getSandboxClaimId, getSandboxPropertyId } = require('tests/integration/utils/sandbox_entities') describe('qualifier set', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should set a qualifier', async () => { const [ guid, property ] = await Promise.all([ getSandboxClaimId(), getSandboxPropertyId('string') ]) const value = randomString() const res = await setQualifier({ guid, property, value }) res.success.should.equal(1) const qualifier = res.claim.qualifiers[property].slice(-1)[0] qualifier.datavalue.value.should.equal(value) }) it('should set a qualifier with a custom calendar', async () => { const [ guid, property ] = await Promise.all([ getSandboxClaimId(), getSandboxPropertyId('time') ]) const res = await setQualifier({ guid, property, value: { time: '1802-02-26', calendar: 'julian' } }) res.success.should.equal(1) const qualifier = res.claim.qualifiers[property].slice(-1)[0] qualifier.datavalue.value.calendarmodel.should.equal('http://www.wikidata.org/entity/Q1985786') }) }) wikibase-edit-5.3.0/tests/integration/qualifier/update.js000066400000000000000000000202701433026310300235220ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const updateQualifier = wbEdit.qualifier.update const { undesiredRes } = require('tests/integration/utils/utils') const { getSandboxPropertyId, getSandboxClaimId, getSandboxItemId, createItem } = require('tests/integration/utils/sandbox_entities') const { addQualifier } = require('tests/integration/utils/sandbox_snaks') const { randomString, randomNumber } = require('tests/unit/utils') const { simplify } = require('wikibase-sdk') // Use years above 1583 to be sure to default to Gregorian calendar const gregorianCalendarYear = 1583 describe('qualifier update', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should update a qualifier', async () => { const oldValue = randomString() const newValue = randomString() const { guid, property } = await addQualifier({ datatype: 'string', value: oldValue }) const res = await updateQualifier({ guid, property, oldValue, newValue }) const updatedQualifier = res.claim.qualifiers[property].slice(-1)[0] updatedQualifier.datavalue.value.should.deepEqual(newValue) }) it('should fetch the properties it needs', done => { const oldValue = randomString() const newValue = randomString() addQualifier({ datatype: 'string', value: oldValue }) .then(({ guid, property, qualifier }) => { return updateQualifier({ guid, property: 'P999999', oldValue, newValue }) .then(undesiredRes(done)) .catch(err => { err.message.should.equal('property not found') done() }) }) .catch(done) }) it('should reject if old value is missing', done => { const oldValue = randomString() const newValue = randomString() Promise.all([ getSandboxClaimId(), getSandboxPropertyId('string') ]) .then(([ guid, property ]) => { return updateQualifier({ guid, property, oldValue, newValue }) .then(undesiredRes(done)) .catch(err => { const possibleMessages = [ 'claim qualifiers not found', 'qualifier not found' ] possibleMessages.includes(err.message).should.be.true() done() }) }) .catch(done) }) it('should update a wikibase-item claim', async () => { const oldValue = await getSandboxItemId() const someItem = await createItem() const newValue = someItem.id const { guid, property } = await addQualifier({ datatype: 'wikibase-item', value: oldValue }) const res = await updateQualifier({ guid, property, oldValue, newValue }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] qualifier.datavalue.value.id.should.deepEqual(newValue) }) it('should update a monolingual text claim', async () => { const oldValue = { text: randomString(), language: 'fr' } const newValue = { text: randomString(), language: 'de' } const { guid, property } = await addQualifier({ datatype: 'monolingualtext', value: oldValue }) const res = await updateQualifier({ guid, property, oldValue, newValue }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] qualifier.datavalue.value.should.deepEqual(newValue) }) it('should update a quantity claim with a unit', async () => { const oldValue = { amount: randomNumber(), unit: 'Q1' } const newValue = { amount: randomNumber(), unit: 'Q2' } const { guid, property } = await addQualifier({ datatype: 'quantity', value: oldValue }) const res = await updateQualifier({ guid, property, oldValue, newValue }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] simplify.qualifier(qualifier, { keepRichValues: true }).should.deepEqual(newValue) }) it('should update a time claim with a day precision', async () => { const oldYear = 1000 + randomNumber(3) const newYear = 1000 + randomNumber(3) const oldValue = `${oldYear}-02-26` const newValue = `${newYear}-10-25` const { guid, property } = await addQualifier({ datatype: 'time', value: oldValue }) const res = await updateQualifier({ guid, property, oldValue, newValue }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] simplify.qualifier(qualifier, { timeConverter: 'simple-day' }).should.equal(newValue) }) it('should update a time claim with a month precision', async () => { const oldValue = `${1000 + randomNumber(3)}-02` const newValue = `${1000 + randomNumber(3)}-03` const { guid, property } = await addQualifier({ datatype: 'time', value: oldValue }) const res = await updateQualifier({ guid, property, oldValue, newValue }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] simplify.qualifier(qualifier, { timeConverter: 'simple-day' }).should.equal(newValue) }) it('should update a time claim with a year precision', async () => { const oldValue = (1000 + randomNumber(3)).toString() const newValue = (1000 + randomNumber(3)).toString() const { guid, property } = await addQualifier({ datatype: 'time', value: oldValue }) const res = await updateQualifier({ guid, property, oldValue, newValue }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] simplify.qualifier(qualifier, { timeConverter: 'simple-day' }).should.equal(newValue) }) it('should update a time claim when passed a rich value', async () => { const oldValue = `${gregorianCalendarYear + randomNumber(3)}-04` const newValue = `${gregorianCalendarYear + randomNumber(3)}-05` const { guid, property } = await addQualifier({ datatype: 'time', value: oldValue }) const richOldValue = { time: `+${oldValue}-01T00:00:00Z`, timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' } const res = await updateQualifier({ guid, property, oldValue: richOldValue, newValue }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] simplify.qualifier(qualifier, { timeConverter: 'simple-day' }).should.equal(newValue) }) it('should update a time claim when passed a rich value with a simplified time value', async () => { const oldValue = `${gregorianCalendarYear + randomNumber(3)}-06` const newValue = `${gregorianCalendarYear + randomNumber(3)}-07` const { guid, property } = await addQualifier({ datatype: 'time', value: oldValue }) const richOldValue = { time: `${oldValue}-01`, timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' } const res = await updateQualifier({ guid, property, oldValue: richOldValue, newValue }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] simplify.qualifier(qualifier, { timeConverter: 'simple-day' }).should.equal(newValue) }) it('should update a globe-coordinate claim', async () => { const oldValue = { latitude: randomNumber(2), longitude: randomNumber(2), precision: 0.01, globe: 'http://www.wikidata.org/entity/Q111' } const newValue = { latitude: randomNumber(2), longitude: randomNumber(2), precision: 0.01, globe: 'http://www.wikidata.org/entity/Q112' } const { guid, property } = await addQualifier({ datatype: 'globe-coordinate', value: oldValue }) const res = await updateQualifier({ guid, property, oldValue, newValue }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] const { value } = qualifier.datavalue value.latitude.should.equal(newValue.latitude) value.longitude.should.equal(newValue.longitude) value.precision.should.equal(newValue.precision) value.globe.should.equal(newValue.globe) }) it('should update a qualifier with a special snaktype', async () => { const oldValue = { snaktype: 'novalue' } const newValue = { snaktype: 'somevalue' } const { guid, property, qualifier } = await addQualifier({ datatype: 'string', value: oldValue }) qualifier.snaktype.should.equal('novalue') const res = await updateQualifier({ guid, property, oldValue, newValue }) const updatedQualifier = res.claim.qualifiers[property].slice(-1)[0] updatedQualifier.snaktype.should.equal('somevalue') }) }) wikibase-edit-5.3.0/tests/integration/reference/000077500000000000000000000000001433026310300216565ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/reference/remove.js000066400000000000000000000020751433026310300235150ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const removeReference = wbEdit.reference.remove const { randomString } = require('tests/unit/utils') const { addReference } = require('tests/integration/utils/sandbox_snaks') describe('reference remove', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should remove a reference', async () => { const { guid, reference } = await addReference({ datatype: 'string', value: randomString() }) const res = await removeReference({ guid, hash: reference.hash }) res.success.should.equal(1) }) it('should remove several qualifiers', async () => { const [ res1, res2 ] = await Promise.all([ addReference({ datatype: 'string', value: randomString() }), addReference({ datatype: 'string', value: randomString() }) ]) const res3 = await removeReference({ guid: res1.guid, hash: [ res1.reference.hash, res2.reference.hash ] }) res3.success.should.equal(1) }) }) wikibase-edit-5.3.0/tests/integration/reference/set.js000066400000000000000000000056221433026310300230140ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) const setReference = wbEdit.reference.set const { randomString } = require('tests/unit/utils') const { getSandboxClaimId, getSandboxPropertyId, getRefreshedClaim } = require('tests/integration/utils/sandbox_entities') describe('reference set', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should set a reference with the property/value interface', async () => { const [ guid, property ] = await Promise.all([ getSandboxClaimId(), getSandboxPropertyId('string') ]) const value = randomString() const res = await setReference({ guid, property, value }) res.success.should.equal(1) res.reference.snaks[property][0].datavalue.value.should.equal(value) }) it('should set a reference with the snaks object interface', async () => { const [ guid, stringProperty, quantityProperty ] = await Promise.all([ getSandboxClaimId(), getSandboxPropertyId('string'), getSandboxPropertyId('quantity') ]) const stringValue = randomString() const quantityValue = Math.random() const snaks = { [stringProperty]: [ { snaktype: 'novalue' }, stringValue ], [quantityProperty]: quantityValue } const res = await setReference({ guid, snaks }) res.success.should.equal(1) res.reference.snaks[stringProperty][0].snaktype.should.equal('novalue') res.reference.snaks[stringProperty][1].datavalue.value.should.equal(stringValue) res.reference.snaks[quantityProperty][0].datavalue.value.amount.should.equal(`+${quantityValue}`) }) it('should update a reference by passing a hash', async () => { const [ guid, stringProperty, quantityProperty ] = await Promise.all([ getSandboxClaimId(), getSandboxPropertyId('string'), getSandboxPropertyId('quantity') ]) const initialClaim = await getRefreshedClaim(guid) const stringValue = randomString() const quantityValue = Math.random() const initialSnaks = { [stringProperty]: { snaktype: 'novalue' } } const res1 = await setReference({ guid, snaks: initialSnaks }) res1.reference.snaks[stringProperty][0].snaktype.should.equal('novalue') const { hash } = res1.reference const updatedSnaks = { [stringProperty]: stringValue, [quantityProperty]: quantityValue } const res2 = await setReference({ guid, hash, snaks: updatedSnaks }, { summary: 'here' }) res2.reference.snaks[stringProperty][0].datavalue.value.should.equal(stringValue) res2.reference.snaks[quantityProperty][0].datavalue.value.amount.should.equal(`+${quantityValue}`) const claim = await getRefreshedClaim(guid) claim.references.length.should.equal(initialClaim.references.length + 1) claim.references.slice(-1)[0].hash.should.equal(res2.reference.hash) }) }) wikibase-edit-5.3.0/tests/integration/sitelink/000077500000000000000000000000001433026310300215425ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/sitelink/set.js000066400000000000000000000020651433026310300226760ustar00rootroot00000000000000require('should') const config = require('config') const wbEdit = require('root')(config) // Those tests require setting an instance with sitelinks // (such as test.wikidata.org) in config, and are thus disabled by default xdescribe('set sitelink', () => { it('should set a sitelink', async () => { const res = await wbEdit.sitelink.set({ id: 'Q224124', site: 'frwiki', title: 'Septembre', badges: 'Q608|Q609', }) res.success.should.equal(1) res.entity.id.should.equal('Q224124') res.entity.sitelinks.frwiki.title.should.equal('Septembre') res.entity.sitelinks.frwiki.badges.should.deepEqual([ 'Q608', 'Q609' ]) }) it('should remove a sitelink', async () => { await wbEdit.sitelink.set({ id: 'Q224124', site: 'eswiki', title: 'Septiembre', }) const res = await wbEdit.sitelink.set({ id: 'Q224124', site: 'eswiki', title: null, }) res.success.should.equal(1) res.entity.id.should.equal('Q224124') res.entity.sitelinks.eswiki.removed.should.equal('') }) }) wikibase-edit-5.3.0/tests/integration/summary.js000066400000000000000000000141101433026310300217500ustar00rootroot00000000000000require('should') const config = require('config') const { instance, credentials } = config const WBEdit = require('root') const { randomString } = require('../unit/utils') const { getLastEditSummary } = require('./utils/utils') const { getSandboxItemId, getSandboxPropertyId, createItem } = require('./utils/sandbox_entities') const { addClaim, addQualifier } = require('./utils/sandbox_snaks') const getProperty = require('./utils/get_property') const params = summary => ({ summary, labels: { en: randomString() } }) describe('summary', function () { this.timeout(20 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) it('should not add a default summary', async () => { const wbEdit = WBEdit({ instance, credentials }) const editSummary = await postAndGetEditSummary(wbEdit) editSummary.should.endWith(' */') }) it('should accept a custom summary in config', async () => { const summary = 'some custom summary' const customConfig = Object.assign({ instance, credentials, summary }) const wbEdit = WBEdit(customConfig) const editSummary = await postAndGetEditSummary(wbEdit) editSummary.should.endWith(` */ ${summary}`) }) it('should accept a custom summary in the request config', async () => { const summary = 'another custom summary' const wbEdit = WBEdit({ instance, credentials, summary: 'global summary' }) const editSummary = await postAndGetEditSummary(wbEdit, { summary }) editSummary.should.endWith(` */ ${summary}`) }) it('should not re-use the previous summary', async () => { const summary = 'another custom summary' const wbEdit = WBEdit({ instance, credentials }) const editSummary = await postAndGetEditSummary(wbEdit, { summary }) editSummary.should.endWith(` */ ${summary}`) const editSummary2 = await postAndGetEditSummary(wbEdit) editSummary2.should.endWith(' */') }) it('should accept a custom summary in the edit object', async () => { const summary = 'and yet another one' const wbEdit = WBEdit({ instance, credentials, summary: 'global summary' }) const editSummary = await postAndGetEditSummary(wbEdit, {}, summary) editSummary.should.endWith(` */ ${summary}`) }) describe('request wrapper commands', () => { it('should accept a custom summary in commands object', async () => { const id = await getSandboxItemId() const value = `Bac à Sable (${randomString()})` const summary = randomString() const wbEdit = WBEdit({ instance, credentials, summary: 'global summary' }) await wbEdit.label.set({ id, language: 'la', value, summary }) const comment = await getLastEditSummary(id) comment.should.endWith(summary) }) }) describe('bundle wrapper commands', () => { const wbEdit = WBEdit({ instance, credentials, summary: 'global summary' }) it('should pass a summary in claim.create', async () => { const [ id, property ] = await Promise.all([ getSandboxItemId(), getSandboxPropertyId('string') ]) const value = randomString() const summary = randomString() await wbEdit.claim.create({ id, property, value, summary }) const comment = await getLastEditSummary(id) comment.should.endWith(summary) }) it('should pass a summary in claim.update', async () => { const oldValue = randomString() const newValue = randomString() const summary = randomString() const { id, property } = await addClaim({ datatype: 'string', value: oldValue }) await wbEdit.claim.update({ id, property, oldValue, newValue, summary }) const comment = await getLastEditSummary(id) comment.should.endWith(summary) }) it('should pass a summary in claim.move targeting a single entity', async () => { const { id } = await createItem() const { guid } = await addClaim({ id, datatype: 'string', value: randomString() }) const { id: otherStringPropertyId } = await getProperty({ datatype: 'string', reserved: true }) const summary = randomString() await wbEdit.claim.move({ guid, id, property: otherStringPropertyId, summary }) const comment = await getLastEditSummary(id) comment.should.endWith(summary) }) it('should pass a summary in claim.move targeting a multiple entities', async () => { const [ { id: idA }, { id: idB } ] = await Promise.all([ createItem(), createItem() ]) const { property, guid } = await addClaim({ id: idA, datatype: 'string', value: randomString() }) const summary = randomString() await wbEdit.claim.move({ guid, id: idB, property, summary }) const commentA = await getLastEditSummary(idA) commentA.should.endWith(summary) const commentB = await getLastEditSummary(idA) commentB.should.endWith(summary) }) it('should pass a summary in qualifier.update', async () => { const oldValue = randomString() const newValue = randomString() const { guid, property } = await addQualifier({ datatype: 'string', value: oldValue }) const summary = randomString() await wbEdit.qualifier.update({ guid, property, oldValue, newValue, summary }) const comment = await getLastEditSummary(guid.split('$')[0]) comment.should.endWith(summary) }) it('should pass a summary in qualifier.move', async () => { const [ valueA, valueB ] = [ randomString(), randomString() ] const { id: oldProperty } = await getProperty({ datatype: 'string', reserved: true }) const { guid, hash } = await addQualifier({ property: oldProperty, value: valueA }) await addQualifier({ guid, property: oldProperty, value: valueB }) const { id: newProperty } = await getProperty({ datatype: 'string', reserved: true }) const summary = randomString() await wbEdit.qualifier.move({ guid, hash, oldProperty, newProperty, summary }) const comment = await getLastEditSummary(guid.split('$')[0]) comment.should.endWith(summary) }) }) }) const postAndGetEditSummary = (wbEdit, reqConfig, paramsArg) => { return wbEdit.entity.create(params(paramsArg), reqConfig) .then(getLastEditSummary) } wikibase-edit-5.3.0/tests/integration/tags.js000066400000000000000000000017561433026310300212250ustar00rootroot00000000000000require('should') const config = require('config') const { wdCredentials: credentials } = config const WBEdit = require('root') const { getLastRevision } = require('./utils/utils') const { randomString } = require('../unit/utils') const sandboxEntityId = 'Q4115189' const instance = 'https://www.wikidata.org' describe('tags', function () { this.timeout(20 * 1000) // Tests disable by default as they need wikidata credentials to be set describe('on wikidata.org', () => { xit('should add a wikibase-edit tag by default', async () => { if (credentials == null) throw new Error('wikidata credentials required for this test (config.wdCredentials)') const wbEdit = WBEdit({ instance, credentials }) const res = await wbEdit.alias.add({ id: sandboxEntityId, language: 'fr', value: randomString() }) res.success.should.equal(1) const revision = await getLastRevision(sandboxEntityId, instance) revision.tags.should.deepEqual([ 'WikibaseJS-edit' ]) }) }) }) wikibase-edit-5.3.0/tests/integration/token_expiration.js000066400000000000000000000031351433026310300236420ustar00rootroot00000000000000require('should') const config = require('config') const { instance, credentials, credentialsAlt } = config const WBEdit = require('root') const { randomString } = require('tests/unit/utils') const { getSandboxItemId } = require('tests/integration/utils/sandbox_entities') const language = 'fr' const { wait } = require('tests/integration/utils/utils') describe('token expiration', function () { this.timeout(24 * 60 * 60 * 1000) before('wait for instance', require('tests/integration/utils/wait_for_instance')) xit('should renew tokens (oauth)', async () => { const wbEdit = WBEdit({ instance, credentials }) const id = await getSandboxItemId() const doActionRequiringAuthPeriodically = async () => { const value = randomString() const res = await wbEdit.alias.add({ id, language, value }) res.success.should.equal(1) console.log(new Date().toISOString(), 'added alias', value) await wait(60 * 1000) return doActionRequiringAuthPeriodically() } await doActionRequiringAuthPeriodically() }) xit('should renew tokens (username/password)', async () => { const wbEdit = WBEdit({ instance, credentials: credentialsAlt }) const id = await getSandboxItemId() const doActionRequiringAuthPeriodically = async () => { const value = randomString() const res = await wbEdit.alias.add({ id, language, value }) res.success.should.equal(1) console.log(new Date().toISOString(), 'added alias', value) await wait(60 * 1000) return doActionRequiringAuthPeriodically() } await doActionRequiringAuthPeriodically() }) }) wikibase-edit-5.3.0/tests/integration/utils/000077500000000000000000000000001433026310300210605ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/integration/utils/get_property.js000066400000000000000000000032301433026310300241370ustar00rootroot00000000000000const config = require('config') const wbk = require('wikibase-sdk')({ instance: config.instance }) const sandboxProperties = {} const fetch = require('lib/request/fetch') const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') module.exports = async ({ datatype, reserved }) => { if (!datatype) throw new Error('missing datatype') if (reserved) return createProperty(datatype) const property = await getProperty(datatype) sandboxProperties[datatype] = property return property } const getProperty = async datatype => { const pseudoPropertyId = getPseudoPropertyId(datatype) const cachedPropertyId = sandboxProperties[pseudoPropertyId] if (cachedPropertyId) return cachedPropertyId const foundPropertyId = await findOnWikibase(pseudoPropertyId) if (foundPropertyId) return foundPropertyId else return createProperty(datatype) } const findOnWikibase = async pseudoPropertyId => { const url = wbk.searchEntities({ search: pseudoPropertyId, type: 'property' }) const body = await fetch(url).then(res => res.json()) const firstWbResult = body.search[0] if (firstWbResult) return firstWbResult } const createProperty = async datatype => { const pseudoPropertyId = getPseudoPropertyId(datatype) const res = await wbEdit.entity.create({ type: 'property', datatype, labels: { // Including a random string to avoid conflicts in case a property with that pseudoPropertyId // already exist but wasn't found due to a problem in ElasticSearch en: `${pseudoPropertyId} (${randomString()})` } }) return res.entity } const getPseudoPropertyId = datatype => `${datatype} sandbox property` wikibase-edit-5.3.0/tests/integration/utils/sandbox_entities.js000066400000000000000000000053471433026310300247710ustar00rootroot00000000000000const config = require('config') const wbk = require('wikibase-sdk')({ instance: config.instance }) const { getEntityIdFromGuid } = wbk const wbEdit = require('root')(config) const { randomString } = require('tests/unit/utils') const getProperty = require('./get_property') const fetch = require('lib/request/fetch') // Working around the circular dependency let addClaim const lateRequire = () => { ({ addClaim } = require('tests/integration/utils/sandbox_snaks')) } setTimeout(lateRequire, 0) const createEntity = async (data = {}) => { data.labels = data.labels || { en: randomString() } const { entity } = await wbEdit.entity.create(data) console.log(`created ${entity.type}`, entity.id, data.datatype || '') return entity } let sandboxItemPromise const getSandboxItem = () => { sandboxItemPromise = sandboxItemPromise || createEntity() return sandboxItemPromise } const getRefreshedEntity = async id => { const url = wbk.getEntities({ ids: id }) const res = await fetch(url).then(res => res.json()) return res.entities[id] } let claimPromise const getSandboxClaim = (datatype = 'string') => { if (claimPromise) return claimPromise claimPromise = Promise.all([ getSandboxItem(), getSandboxPropertyId(datatype) ]) .then(([ item, propertyId ]) => { const propertyClaims = item.claims[propertyId] if (propertyClaims) return propertyClaims[0] return wbEdit.claim.create({ id: item.id, property: propertyId, value: randomString() }) .then(res => res.claim) }) return claimPromise } const getRefreshedClaim = async guid => { const id = getEntityIdFromGuid(guid) const { claims } = await getRefreshedEntity(id) for (const propertyClaims of Object.values(claims)) { for (const claim of propertyClaims) { if (claim.id === guid) return claim } } } const getSandboxItemId = () => getSandboxItem().then(getId) const getSandboxPropertyId = datatype => getProperty({ datatype }).then(getId) const getSandboxClaimId = () => getSandboxClaim().then(getId) const getId = obj => obj.id const createItem = (data = {}) => { data.type = 'item' return createEntity(data) } let someEntityId const getSomeEntityId = async () => { someEntityId = someEntityId || await getSandboxItemId() return someEntityId } let someGuid const getSomeGuid = async () => { if (someGuid) return someGuid const { guid } = await addClaim({ datatype: 'string', value: randomString() }) someGuid = guid return guid } module.exports = { getSandboxItem, getSandboxItemId, getReservedItem: createItem, getReservedItemId: () => createItem().then(entity => entity.id), getSandboxPropertyId, getRefreshedEntity, getRefreshedClaim, getSandboxClaim, getSandboxClaimId, createItem, getSomeEntityId, getSomeGuid } wikibase-edit-5.3.0/tests/integration/utils/sandbox_snaks.js000066400000000000000000000027551433026310300242640ustar00rootroot00000000000000const config = require('config') const wbEdit = require('root')(config) const { getSandboxItemId, getSandboxPropertyId, getSandboxClaimId } = require('./sandbox_entities') const { randomString } = require('tests/unit/utils') const addClaim = async (params = {}) => { let { id, property, datatype = 'string', value = randomString(), qualifiers } = params id = id || await getSandboxItemId() property = property || await getSandboxPropertyId(datatype) const res = await wbEdit.entity.edit({ id, claims: { [property]: { value, qualifiers } } }) const claim = res.entity.claims[property].slice(-1)[0] return { id, property, claim, guid: claim.id } } const addQualifier = async ({ guid, property, datatype, value }) => { guid = guid || await getSandboxClaimId() property = property || await getSandboxPropertyId(datatype) const res = await wbEdit.qualifier.set({ guid, property, value }) const qualifier = res.claim.qualifiers[property].slice(-1)[0] const { hash } = qualifier return { guid, property, qualifier, hash } } const addReference = async ({ guid, property, datatype, value }) => { guid = guid || await getSandboxClaimId() property = property || await getSandboxPropertyId(datatype) const { reference } = await wbEdit.reference.set({ guid, property, value }) const referenceSnak = reference.snaks[property].slice(-1)[0] return { guid, property, reference, referenceSnak } } module.exports = { addClaim, addQualifier, addReference } wikibase-edit-5.3.0/tests/integration/utils/utils.js000066400000000000000000000043041433026310300225570ustar00rootroot00000000000000const { yellow } = require('chalk') const { instance } = require('config') const WBK = require('wikibase-sdk') const wbk = WBK({ instance }) const fetch = require('lib/request/fetch') const resolveTitle = require('lib/resolve_title') const getRevisions = async ({ id, customInstance, limit, props }) => { customInstance = customInstance || instance const title = await resolveTitle(id, `${customInstance}/w/api.php`) const customWbk = WBK({ instance: customInstance }) const url = customWbk.getRevisions(title, { limit, props }) const { query } = await fetch(url).then(res => res.json()) return Object.values(query.pages)[0].revisions } const getLastRevision = async (id, customInstance) => { const revisions = await getRevisions({ id, customInstance, limit: 1, props: [ 'comment', 'tags' ] }) return revisions[0] } module.exports = { wait: ms => new Promise(resolve => setTimeout(resolve, ms)), getEntity: async id => { const url = wbk.getEntities({ ids: id }) const { entities } = await fetch(url).then(res => res.json()) return entities[id] }, getEntityHistory: async (id, customInstance) => { const revisions = await getRevisions({ id, customInstance }) return revisions.sort(chronologically) }, getLastRevision, getLastEditSummary: async id => { if (typeof id === 'object' && id.entity) id = id.entity.id const revision = await getLastRevision(id) return revision.comment }, // A function to quickly fail when a test gets an undesired positive answer undesiredRes: done => res => { console.warn(yellow('undesired positive res:'), res) done(new Error('.then function was expected not to be called')) }, // Same but for async/await tests that don't use done shouldNotBeCalled: res => { console.warn(yellow('undesired positive res:'), res) const err = new Error('function was expected not to be called') err.name = 'shouldNotBeCalled' err.context = { res } throw err }, rethrowShouldNotBeCalledErrors: err => { if (err.name === 'shouldNotBeCalled') throw err }, // See /wiki/Special:BotPasswords isBotPassword: password => password.match(/^\w+@[a-z0-9]{32}$/) } const chronologically = (a, b) => a.revid - b.revid wikibase-edit-5.3.0/tests/integration/utils/wait_for_instance.js000066400000000000000000000007051433026310300251160ustar00rootroot00000000000000const { instance } = require('config') const fetch = require('lib/request/fetch') const { yellow, grey } = require('chalk') const { wait } = require('tests/integration/utils/utils') module.exports = () => { const check = async () => { return fetch(instance, { timeout: 2000 }) .catch(err => { console.warn(yellow(`waiting for instance at ${instance}`, grey(err.code))) return wait(1000).then(check) }) } return check() } wikibase-edit-5.3.0/tests/unit/000077500000000000000000000000001433026310300163545ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/unit/alias/000077500000000000000000000000001433026310300174455ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/unit/alias/add.js000066400000000000000000000022051433026310300205320ustar00rootroot00000000000000require('module-alias/register') require('should') const addAlias = require('lib/alias/add') const { randomString, someEntityId } = require('tests/unit/utils') const language = 'it' describe('alias add', () => { it('should reject if not passed an entity', () => { addAlias.bind(null, {}).should.throw('invalid entity') }) it('should reject if not passed a language', () => { addAlias.bind(null, { id: someEntityId }).should.throw('invalid language') }) it('should reject if not passed an alias', () => { addAlias.bind(null, { id: someEntityId, language }).should.throw('empty alias array') }) it('should accept a single alias string', () => { const value = randomString() const { action, data } = addAlias({ id: someEntityId, language, value }) action.should.equal('wbsetaliases') data.add.should.deepEqual(value) }) it('should accept multiple aliases as an array of strings', () => { const value = [ randomString(), randomString() ] const { action, data } = addAlias({ id: someEntityId, language, value }) action.should.equal('wbsetaliases') data.add.should.equal(value.join('|')) }) }) wikibase-edit-5.3.0/tests/unit/alias/remove.js000066400000000000000000000024561433026310300213070ustar00rootroot00000000000000require('module-alias/register') require('should') const removeAlias = require('lib/alias/remove') const { randomString, someEntityId } = require('tests/unit/utils') const language = 'it' describe('alias remove', () => { it('should reject if not passed an entity', () => { removeAlias.bind(null, {}).should.throw('invalid entity') }) it('should reject if not passed a language', () => { removeAlias.bind(null, { id: someEntityId }).should.throw('invalid language') }) it('should reject if not passed an alias', () => { removeAlias.bind(null, { id: someEntityId, language }).should.throw('empty alias array') }) it('should accept a single alias string', () => { // It's not necessary that the removed alias actually exist // so we can just pass a random string and expect Wikibase to deal with it const value = randomString() const { action, data } = removeAlias({ id: someEntityId, language, value }) action.should.equal('wbsetaliases') data.remove.should.equal(value) }) it('should accept multiple aliases as an array of strings', () => { const value = [ randomString(), randomString() ] const { action, data } = removeAlias({ id: someEntityId, language, value }) action.should.equal('wbsetaliases') data.remove.should.equal(value.join('|')) }) }) wikibase-edit-5.3.0/tests/unit/alias/set.js000066400000000000000000000021771433026310300206050ustar00rootroot00000000000000require('module-alias/register') require('should') const setAlias = require('lib/alias/set') const { randomString, someEntityId } = require('tests/unit/utils') const language = 'it' describe('alias set', () => { it('should reject if not passed an entity', () => { setAlias.bind(null, {}).should.throw('invalid entity') }) it('should reject if not passed a language', () => { setAlias.bind(null, { id: someEntityId }).should.throw('invalid language') }) it('should reject if not passed an alias', () => { setAlias.bind(null, { id: someEntityId, language }).should.throw('empty alias array') }) it('should accept a single alias string', () => { const value = randomString() const { action, data } = setAlias({ id: someEntityId, language, value }) action.should.equal('wbsetaliases') data.should.be.an.Object() }) it('should accept multiple aliases as an array of strings', () => { const value = [ randomString(), randomString() ] const { action, data } = setAlias({ id: someEntityId, language, value }) action.should.equal('wbsetaliases') data.set.should.equal(value.join('|')) }) }) wikibase-edit-5.3.0/tests/unit/claim/000077500000000000000000000000001433026310300174415ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/unit/claim/remove.js000066400000000000000000000013111433026310300212700ustar00rootroot00000000000000require('module-alias/register') require('should') const removeClaim = require('lib/claim/remove') const { guid, guid2 } = require('tests/unit/utils') describe('claim remove', () => { it('should set the action to wbremoveclaims', async () => { const { action } = await removeClaim({ guid }) action.should.equal('wbremoveclaims') }) it('should return formatted data for one claim', async () => { const { data } = await removeClaim({ guid }) data.claim.should.equal(guid) }) it('should return formatted data for several claims', async () => { const guids = [ guid, guid2 ] const { data } = await removeClaim({ guid: guids }) data.claim.should.equal(guids.join('|')) }) }) wikibase-edit-5.3.0/tests/unit/claim/set.js000066400000000000000000000014731433026310300205770ustar00rootroot00000000000000require('module-alias/register') require('should') const setClaim = require('lib/claim/set') const { guid, sandboxStringProp: property, properties } = require('tests/unit/utils') describe('claim set', () => { it('should set the action to wbsetclaim', () => { const { action } = setClaim({ guid, property, value: 'foo' }, properties) action.should.equal('wbsetclaim') }) it('should return formatted claim data for a string', () => { const { data } = setClaim({ guid, property, value: 'foo' }, properties) JSON.parse(data.claim).should.deepEqual({ id: guid, type: 'statement', mainsnak: { snaktype: 'value', property, datavalue: { value: 'foo', type: 'string' } } }) }) }) wikibase-edit-5.3.0/tests/unit/claim/time.js000066400000000000000000000314641433026310300207450ustar00rootroot00000000000000require('module-alias/register') require('should') const getTimeObject = require('lib/claim/get_time_object') describe('claim time', () => { it('should parse year without precision', () => { getTimeObject('2018').should.deepEqual({ time: '+2018-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject('-2018').should.deepEqual({ time: '-2018-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse explicitly positive year', () => { getTimeObject('+2018').should.deepEqual({ time: '+2018-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) }) it('should parse month without precision', () => { getTimeObject('2018-03').should.deepEqual({ time: '+2018-03-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject('-2018-03').should.deepEqual({ time: '-2018-03-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse day without precision', () => { getTimeObject('2018-03-03').should.deepEqual({ time: '+2018-03-03T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject('-2018-03-03').should.deepEqual({ time: '-2018-03-03T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse year with precision', () => { getTimeObject({ time: '2018', precision: 9 }).should.deepEqual({ time: '+2018-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject({ time: '-2018', precision: 9 }).should.deepEqual({ time: '-2018-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse month with precision', () => { getTimeObject({ time: '2018-03', precision: 10 }).should.deepEqual({ time: '+2018-03-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject({ time: '-2018-03', precision: 10 }).should.deepEqual({ time: '-2018-03-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse day with precision', () => { getTimeObject({ time: '2018-03-03', precision: 11 }).should.deepEqual({ time: '+2018-03-03T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject({ time: '-2018-03-03', precision: 11 }).should.deepEqual({ time: '-2018-03-03T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse hour with precision', () => { getTimeObject({ time: '2018-03-03T11:00:00Z', precision: 12 }).should.deepEqual({ time: '+2018-03-03T11:00:00Z', timezone: 0, before: 0, after: 0, precision: 12, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject({ time: '-2018-03-03T11:00:00Z', precision: 12 }).should.deepEqual({ time: '-2018-03-03T11:00:00Z', timezone: 0, before: 0, after: 0, precision: 12, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse minute with precision', () => { getTimeObject({ time: '2018-03-03T11:22:00Z', precision: 13 }).should.deepEqual({ time: '+2018-03-03T11:22:00Z', timezone: 0, before: 0, after: 0, precision: 13, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject({ time: '-2018-03-03T11:22:00Z', precision: 13 }).should.deepEqual({ time: '-2018-03-03T11:22:00Z', timezone: 0, before: 0, after: 0, precision: 13, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse second with precision', () => { getTimeObject({ time: '2018-03-03T11:22:33Z', precision: 14 }).should.deepEqual({ time: '+2018-03-03T11:22:33Z', timezone: 0, before: 0, after: 0, precision: 14, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject({ time: '-2018-03-03T11:22:33Z', precision: 14 }).should.deepEqual({ time: '-2018-03-03T11:22:33Z', timezone: 0, before: 0, after: 0, precision: 14, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse decade with precision', () => { getTimeObject({ time: '2010', precision: 8 }).should.deepEqual({ time: '+2010-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 8, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject({ time: '-2010', precision: 8 }).should.deepEqual({ time: '-2010-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 8, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse century with precision', () => { getTimeObject({ time: '2100', precision: 7 }).should.deepEqual({ time: '+2100-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 7, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject({ time: '-2100', precision: 7 }).should.deepEqual({ time: '-2100-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 7, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse millenium with precision', () => { getTimeObject({ time: '2000', precision: 6 }).should.deepEqual({ time: '+2000-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 6, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) }) it('should parse ten thousand years with precision', () => { getTimeObject({ time: '-10000', precision: 5 }).should.deepEqual({ time: '-10000-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 5, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse ten thousand years without precision and assume its years', () => { getTimeObject('-10000').should.deepEqual({ time: '-10000-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) getTimeObject('10000').should.deepEqual({ time: '+10000-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) }) it('should parse hundred thousand years with precision', () => { getTimeObject({ time: '-2500000', precision: 4 }).should.deepEqual({ time: '-2500000-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 4, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse million years with precision', () => { getTimeObject({ time: '-13798000000', precision: 3 }).should.deepEqual({ time: '-13798000000-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 3, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse billion years with precision', () => { getTimeObject({ time: '-5000000000', precision: 0 }).should.deepEqual({ time: '-5000000000-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 0, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should parse low precision time with too much precision', () => { getTimeObject({ time: '2100-01-23', precision: 7 }).should.deepEqual({ time: '+2100-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 7, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) }) it('should set custom calendar', () => { const gregorian = 'http://www.wikidata.org/entity/Q1985727' const julian = 'http://www.wikidata.org/entity/Q1985786' getTimeObject({ time: '2020' }).calendarmodel.should.equal(gregorian) getTimeObject({ time: '2020', calendar: 'gregorian' }).calendarmodel.should.equal(gregorian) getTimeObject({ time: '2020', calendar: 'Q1985727' }).calendarmodel.should.equal(gregorian) getTimeObject({ time: '2020', calendar: gregorian }).calendarmodel.should.equal(gregorian) getTimeObject({ time: '2020', calendar: 'julian' }).calendarmodel.should.equal(julian) getTimeObject({ time: '2020', calendar: 'Q1985786' }).calendarmodel.should.equal(julian) getTimeObject({ time: '2020', calendar: julian }).calendarmodel.should.equal(julian) }) it('should accept full rich value', () => { getTimeObject({ time: '2018-04-15T00:00:00.000Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }).should.deepEqual({ time: '+2018-04-15T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) }) it('should accept setting month precision for times that specify a day', () => { getTimeObject({ time: '2018-04-15T00:00:00.000Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }).should.deepEqual({ time: '+2018-04-15T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) }) it('should default to julian calendar for dates before 1582, 1582 included', () => { getTimeObject('1582').should.deepEqual({ time: '+1582-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) getTimeObject('1582-11').should.deepEqual({ time: '+1582-11-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) getTimeObject('1582-12-04').should.deepEqual({ time: '+1582-12-04T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) getTimeObject({ time: '+1582-12-04T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }).should.deepEqual({ time: '+1582-12-04T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985786' }) }) it('should default to gregorian calendar for dates after 1582', () => { getTimeObject('1583').should.deepEqual({ time: '+1583-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject('1583-02').should.deepEqual({ time: '+1583-02-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject('1583-10-05').should.deepEqual({ time: '+1583-10-05T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) getTimeObject({ time: '+1583-10-05T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }).should.deepEqual({ time: '+1583-10-05T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' }) }) }) wikibase-edit-5.3.0/tests/unit/description/000077500000000000000000000000001433026310300206775ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/unit/description/set.js000066400000000000000000000017661433026310300220420ustar00rootroot00000000000000require('module-alias/register') require('should') const language = 'fr' const setDescription = require('lib/description/set') const { randomString, someEntityId } = require('tests/unit/utils') describe('description', () => { it('should throw if not passed an entity', () => { setDescription.bind(null, {}).should.throw('invalid entity') }) it('should throw if not passed a language', () => { setDescription.bind(null, { id: someEntityId }).should.throw('invalid language') }) it('should throw if not passed a description', () => { setDescription.bind(null, { id: someEntityId, language }) .should.throw('missing description') }) it('should return an action and data', () => { const value = `Bac à Sable (${randomString()})` const { action, data } = setDescription({ id: someEntityId, language, value }) action.should.equal('wbsetdescription') data.id.should.equal(someEntityId) data.language.should.equal(language) data.value.should.equal(value) }) }) wikibase-edit-5.3.0/tests/unit/entity/000077500000000000000000000000001433026310300176705ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/unit/entity/create.js000066400000000000000000000060121433026310300214700ustar00rootroot00000000000000require('should') const { instance } = require('config') const { randomString, properties } = require('tests/unit/utils') const _createEntity = require('lib/entity/create') const { shouldNotBeCalled } = require('root/tests/integration/utils/utils') const createEntity = params => _createEntity(params, properties, instance) describe('entity create', async () => { it('should reject parameters with an id', async () => { const params = { id: 'Q3' } await createEntity(params) .then(shouldNotBeCalled) .catch(err => err.message.should.equal("a new entity can't already have an id")) }) it('should set the action to wbeditentity', async () => { const params = { labels: { fr: 'foo' } } const { action } = await createEntity(params) action.should.equal('wbeditentity') }) it('should then use entity.edit validation features', async () => { const params = { claims: { P2: 'bla' } } await createEntity(params) .then(shouldNotBeCalled) .catch(err => err.message.should.equal('invalid entity value')) }) it('should format an item', async () => { const label = randomString() const description = randomString() const frAlias = randomString() const enAlias = randomString() const params = { labels: { en: label }, aliases: { fr: frAlias, en: [ enAlias ] }, descriptions: { fr: description }, claims: { P2: 'Q166376' } } const { data } = await createEntity(params) data.new.should.equal('item') JSON.parse(data.data).should.deepEqual({ labels: { en: { language: 'en', value: label } }, aliases: { fr: [ { language: 'fr', value: frAlias } ], en: [ { language: 'en', value: enAlias } ] }, descriptions: { fr: { language: 'fr', value: description } }, claims: { P2: [ { rank: 'normal', type: 'statement', mainsnak: { property: 'P2', snaktype: 'value', datavalue: { type: 'wikibase-entityid', value: { 'entity-type': 'item', 'numeric-id': 166376 } } } } ] } }) }) it('should reject a property creation without type', async () => { await createEntity({ datatype: 'string' }) .then(shouldNotBeCalled) .catch(err => err.message.should.equal("an item can't have a datatype")) }) it('should reject a property creation without datatype', async () => { await createEntity({ type: 'property' }) .then(shouldNotBeCalled) .catch(err => err.message.should.equal('missing property datatype')) }) it('should create a property', async () => { const label = randomString() const params = { type: 'property', datatype: 'string', labels: { en: label } } const { data } = await createEntity(params) data.new.should.equal('property') JSON.parse(data.data).should.deepEqual({ datatype: 'string', labels: { en: { language: 'en', value: label } } }) }) }) wikibase-edit-5.3.0/tests/unit/entity/delete.js000066400000000000000000000012221433026310300214650ustar00rootroot00000000000000require('module-alias/register') require('should') const { someEntityId: id } = require('tests/unit/utils') const deleteEntity = require('lib/entity/delete') describe('entity delete', () => { it('should set the action to delete', () => { deleteEntity({ id }).action.should.equal('delete') }) it('should set the title to the entity id', () => { deleteEntity({ id }).data.title.should.equal(id) }) it('should reject invalid entity ids', () => { deleteEntity.bind(null, { id: 'bla' }).should.throw() deleteEntity.bind(null, { id: 'Item:Q1' }).should.throw() deleteEntity.bind(null, { id: 'Property:P1' }).should.throw() }) }) wikibase-edit-5.3.0/tests/unit/entity/edit.js000066400000000000000000000340031433026310300211530ustar00rootroot00000000000000require('should') const { instance } = require('config') const { randomString, someEntityId: id, properties } = require('tests/unit/utils') const _editEntity = require('lib/entity/edit') const { shouldNotBeCalled } = require('root/tests/integration/utils/utils') const editEntity = params => _editEntity(params, properties, instance) describe('entity edit', () => { it('should reject a missing id', async () => { const params = { claims: { someWikibaseItemPropertyId: 'bla' } } await editEntity(params) .then(shouldNotBeCalled) .catch(err => err.message.should.equal('invalid entity id')) }) it('should reject misplaced parameters', async () => { const params = { id, P2: 'bla' } await editEntity(params) .then(shouldNotBeCalled) .catch(err => { err.message.should.startWith('invalid parameter') err.context.parameter.should.equal('P2') }) }) it('should reject an edit without data', async () => { const params = { id } await editEntity(params) .then(shouldNotBeCalled) .catch(err => err.message.should.equal('no data was passed')) }) it('should reject invalid labels', async () => { const params = { id, labels: { fr: '' } } await editEntity(params) .then(shouldNotBeCalled) .catch(err => err.message.should.equal('invalid label')) }) it('should reject invalid descriptions', async () => { const params = { id, descriptions: { fr: '' } } await editEntity(params) .then(shouldNotBeCalled) .catch(err => err.message.should.equal('invalid description')) }) it('should set the action to wbeditentity', async () => { const params = { id, labels: { fr: 'foo' } } const { action } = await editEntity(params) action.should.equal('wbeditentity') }) it('should return formatted data', async () => { const label = randomString() const description = randomString() const frAlias = randomString() const enAlias = randomString() const { data } = await editEntity({ id, labels: { fr: label }, aliases: { fr: frAlias, en: [ enAlias ] }, descriptions: { fr: description }, claims: { P2: 'Q3576110' } }) data.id.should.equal(id) JSON.parse(data.data).should.deepEqual({ labels: { fr: { language: 'fr', value: label } }, aliases: { fr: [ { language: 'fr', value: frAlias } ], en: [ { language: 'en', value: enAlias } ] }, descriptions: { fr: { language: 'fr', value: description } }, claims: { P2: [ { rank: 'normal', type: 'statement', mainsnak: { property: 'P2', snaktype: 'value', datavalue: { type: 'wikibase-entityid', value: { 'entity-type': 'item', 'numeric-id': 3576110 } } } } ] } }) }) describe('claims', () => { it('should reject invalid claims', async () => { const params = { id, claims: { P2: 'bla' } } await editEntity(params) .then(shouldNotBeCalled) .catch(err => err.message.should.equal('invalid entity value')) }) it('should format an entity claim with qualifiers', async () => { const { data } = await editEntity({ id, claims: { P2: [ { value: 'Q5111731', qualifiers: { P1: '17', P2: [ 'Q13406268' ] } }, { value: 'Q2622002', qualifiers: { P4: '1789-08-04', P8: { amount: 9001, unit: 'Q7727' }, P9: { text: 'bulgroz', language: 'fr' } } } ] } }) JSON.parse(data.data).claims.P2.should.deepEqual([ { rank: 'normal', type: 'statement', mainsnak: { property: 'P2', snaktype: 'value', datavalue: { type: 'wikibase-entityid', value: { 'entity-type': 'item', 'numeric-id': 5111731 } } }, qualifiers: { P1: [ { property: 'P1', snaktype: 'value', datavalue: { type: 'string', value: '17' } } ], P2: [ { property: 'P2', snaktype: 'value', datavalue: { type: 'wikibase-entityid', value: { 'entity-type': 'item', 'numeric-id': 13406268 } } } ] } }, { rank: 'normal', type: 'statement', mainsnak: { property: 'P2', snaktype: 'value', datavalue: { type: 'wikibase-entityid', value: { 'entity-type': 'item', 'numeric-id': 2622002 } } }, qualifiers: { P4: [ { property: 'P4', snaktype: 'value', datavalue: { type: 'time', value: { time: '+1789-08-04T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' } } } ], P8: [ { property: 'P8', snaktype: 'value', datavalue: { type: 'quantity', value: { amount: '+9001', unit: `${instance.replace('https:', 'http:')}/entity/Q7727` } } } ], P9: [ { property: 'P9', snaktype: 'value', datavalue: { type: 'monolingualtext', value: { text: 'bulgroz', language: 'fr' } } } ] } } ]) }) it('should format an entity claim with rich qualifier', async () => { const qualifiers = { P8: [ { value: { amount: 100, unit: 'Q6982035' } } ] } const { data } = await editEntity({ id, claims: { P2: [ { value: 'Q54173', qualifiers } ] } }) JSON.parse(data.data).claims.P2[0].qualifiers.P8[0].should.deepEqual({ property: 'P8', snaktype: 'value', datavalue: { type: 'quantity', value: { amount: '+100', unit: `${instance.replace('https:', 'http:')}/entity/Q6982035` } } }) }) it('should format a rich-value time claim', async () => { const { data } = await editEntity({ id, claims: { P4: [ { time: '1802-02-26', precision: 11 } ] } }) JSON.parse(data.data).claims.P4[0].should.deepEqual({ rank: 'normal', type: 'statement', mainsnak: { property: 'P4', snaktype: 'value', datavalue: { type: 'time', value: { time: '+1802-02-26T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' } } } }) }) it('should format a rich-value time claim with precision 10 or less', async () => { const { data } = await editEntity({ id, claims: { P4: [ { time: '1802-02-00', precision: 10 }, { time: '1802-00-00', precision: 9 } ] } }) JSON.parse(data.data).claims.P4.should.deepEqual([ { rank: 'normal', type: 'statement', mainsnak: { property: 'P4', snaktype: 'value', datavalue: { type: 'time', value: { time: '+1802-02-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 10, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' } } } }, { rank: 'normal', type: 'statement', mainsnak: { property: 'P4', snaktype: 'value', datavalue: { type: 'time', value: { time: '+1802-00-00T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 9, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' } } } } ]) }) it('should format an entity claim with a special snaktype', async () => { const { data } = await editEntity({ id, claims: { P2: [ { value: { snaktype: 'somevalue' } } ], P3: [ { snaktype: 'novalue' } ], } }) JSON.parse(data.data).claims.P2[0].mainsnak.should.deepEqual({ property: 'P2', snaktype: 'somevalue' }) JSON.parse(data.data).claims.P3[0].mainsnak.should.deepEqual({ property: 'P3', snaktype: 'novalue' }) }) it('should format an entity claim with a qualifier with a special snaktype', async () => { const qualifiers = { P4: { snaktype: 'somevalue' } } const { data } = await editEntity({ id, claims: { P2: [ { value: 'Q54173', qualifiers } ] } }) JSON.parse(data.data).claims.P2[0].qualifiers.P4[0].should.deepEqual({ property: 'P4', snaktype: 'somevalue' }) }) it('should format an entity claim with a low precision time claim', async () => { const qualifiers = { P4: { value: '2019-04-01T00:00:00.000Z' } } const { data } = await editEntity({ id, claims: { P2: [ { value: 'Q54173', qualifiers } ] } }) JSON.parse(data.data).claims.P2[0].qualifiers.P4[0].should.deepEqual({ property: 'P4', snaktype: 'value', datavalue: { type: 'time', value: { time: '+2019-04-01T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' } } }) }) it('should format an entity claim with a time qualifier', async () => { const qualifiers = { P4: { value: '2019-04-01T00:00:00.000Z' } } const { data } = await editEntity({ id, claims: { P2: [ { value: 'Q54173', qualifiers } ] } }) JSON.parse(data.data).claims.P2[0].qualifiers.P4[0].should.deepEqual({ property: 'P4', snaktype: 'value', datavalue: { type: 'time', value: { time: '+2019-04-01T00:00:00Z', timezone: 0, before: 0, after: 0, precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985727' } } }) }) it('should format an entity claim with a reference', async () => { const reference = { P7: 'https://example.org', P2: 'Q8447' } const { data } = await editEntity({ id, claims: { P2: { value: 'Q2622002', references: reference } } }) JSON.parse(data.data).claims.P2[0].references[0].snaks.should.deepEqual([ { property: 'P7', snaktype: 'value', datavalue: { type: 'string', value: 'https://example.org' } }, { property: 'P2', snaktype: 'value', datavalue: { type: 'wikibase-entityid', value: { 'entity-type': 'item', 'numeric-id': 8447 } } } ]) }) it('should format an entity claim with a reference formatted with a snaks object', async () => { const reference = { snaks: { P7: 'https://example.org', P2: 'Q8447' } } const { data } = await editEntity({ id, claims: { P2: { value: 'Q2622002', references: [ reference ] } } }) JSON.parse(data.data).claims.P2[0].references[0].snaks[0] .datavalue.value.should.equal('https://example.org') }) }) describe('sitelinks', () => { it('should format simplified sitelinks', async () => { const { data } = await editEntity({ id, sitelinks: { frwiki: 'foo' } }) JSON.parse(data.data).sitelinks.should.deepEqual({ frwiki: { site: 'frwiki', title: 'foo' } }) }) it('should format rich sitelinks', async () => { const { data } = await editEntity({ id, sitelinks: { frwiki: { title: 'foo' } } }) JSON.parse(data.data).sitelinks.should.deepEqual({ frwiki: { site: 'frwiki', title: 'foo' } }) }) it('should format sitelinks with badges', async () => { const { data } = await editEntity({ id, sitelinks: { frwiki: { title: 'foo', badges: [ 'Q608', 'Q609' ], } } }) JSON.parse(data.data).sitelinks.should.deepEqual({ frwiki: { site: 'frwiki', title: 'foo', badges: [ 'Q608', 'Q609' ], } }) }) it('should format sitelinks with stringified badges', async () => { const { data } = await editEntity({ id, sitelinks: { frwiki: { title: 'foo', badges: 'Q608|Q609', } } }) JSON.parse(data.data).sitelinks.should.deepEqual({ frwiki: { site: 'frwiki', title: 'foo', badges: [ 'Q608', 'Q609' ], } }) }) }) }) wikibase-edit-5.3.0/tests/unit/entity/merge.js000066400000000000000000000012321433026310300213230ustar00rootroot00000000000000require('module-alias/register') require('should') const mergeEntity = require('lib/entity/merge') describe('entity merge', () => { describe('items', () => { it('should set the action to wbmergeitems', () => { mergeEntity({ from: 'Q1', to: 'Q2' }).action.should.equal('wbmergeitems') }) it('should reject invalid item ids', () => { mergeEntity.bind(null, { from: 'Q1', to: 'P' }).should.throw() mergeEntity.bind(null, { from: '1', to: 'Q2' }).should.throw() }) }) describe('properties', () => { it('should reject properties', () => { mergeEntity.bind(null, { from: 'P1', to: 'P2' }).should.throw() }) }) }) wikibase-edit-5.3.0/tests/unit/general.js000066400000000000000000000027201433026310300203300ustar00rootroot00000000000000require('module-alias/register') require('should') const wdEdit = require('root') describe('general', () => { it('should return an object', () => { wdEdit({}).should.be.an.Object() }) it('should have label functions', () => { wdEdit({}).label.set.should.be.a.Function() }) it('should have description functions', () => { wdEdit({}).description.set.should.be.a.Function() }) it('should have alias functions', () => { wdEdit({}).alias.set.should.be.a.Function() wdEdit({}).alias.add.should.be.a.Function() wdEdit({}).alias.remove.should.be.a.Function() }) it('should have claim functions', () => { wdEdit({}).claim.create.should.be.a.Function() wdEdit({}).claim.update.should.be.a.Function() wdEdit({}).claim.remove.should.be.a.Function() }) it('should have qualifier functions', () => { wdEdit({}).qualifier.set.should.be.a.Function() wdEdit({}).qualifier.update.should.be.a.Function() wdEdit({}).qualifier.remove.should.be.a.Function() }) it('should have reference functions', () => { wdEdit({}).reference.set.should.be.a.Function() wdEdit({}).qualifier.update.should.be.a.Function() wdEdit({}).reference.remove.should.be.a.Function() }) it('should have entity functions', () => { wdEdit({}).entity.create.should.be.a.Function() wdEdit({}).entity.edit.should.be.a.Function() }) it('should have auth functions', () => { wdEdit({}).getAuthData.should.be.a.Function() }) }) wikibase-edit-5.3.0/tests/unit/label/000077500000000000000000000000001433026310300174335ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/unit/label/set.js000066400000000000000000000016651433026310300205740ustar00rootroot00000000000000require('module-alias/register') require('should') const language = 'fr' const setLabel = require('lib/label/set') const { randomString, someEntityId } = require('tests/unit/utils') describe('label', () => { it('should throw if not passed an entity', () => { setLabel.bind(null, {}).should.throw('invalid entity') }) it('should throw if not passed a language', () => { setLabel.bind(null, { id: someEntityId }).should.throw('invalid language') }) it('should throw if not passed a label', () => { setLabel.bind(null, { id: someEntityId, language }).should.throw('missing label') }) it('should return an action and data', () => { const value = `Bac à Sable (${randomString()})` const { action, data } = setLabel({ id: someEntityId, language, value }) action.should.equal('wbsetlabel') data.id.should.equal(someEntityId) data.language.should.equal(language) data.value.should.equal(value) }) }) wikibase-edit-5.3.0/tests/unit/module_paths.js000066400000000000000000000007451433026310300214040ustar00rootroot00000000000000require('should') describe('module paths', () => { // Module aliases are reserved for tests, to not add another production dependency it('should not require module aliases', () => { require('module-alias').reset() resetRequireCache() const wbEdit = require('../..') console.log('wbEdit', wbEdit) }) }) const resetRequireCache = () => { Object.keys(require.cache) .filter(key => key.includes('wikibase-edit')) .forEach(key => delete require.cache[key]) } wikibase-edit-5.3.0/tests/unit/parse_instance.js000066400000000000000000000017651433026310300217210ustar00rootroot00000000000000require('module-alias/register') require('should') const parseInstance = require('lib/parse_instance') const instance = 'https://hello.bla' const apiEndpoint = `${instance}/w/api.php` describe('parseInstance', () => { it('reject a missing instance', () => { parseInstance.bind(null, {}).should.throw('missing config parameter: instance') }) it('should return an instance and sparql endpoint', () => { const configA = { instance } const configB = { instance: apiEndpoint } parseInstance(configA) parseInstance(configB) configA.instance.should.equal(instance) configB.instance.should.equal(instance) }) it('should allow to customize the script path', () => { const configA = { instance, wgScriptPath: 'foo' } const configB = { instance, wgScriptPath: '/foo' } parseInstance(configA) configA.instanceApiEndpoint.should.equal(`${instance}/foo/api.php`) parseInstance(configB) configB.instanceApiEndpoint.should.equal(`${instance}/foo/api.php`) }) }) wikibase-edit-5.3.0/tests/unit/qualifier/000077500000000000000000000000001433026310300203355ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/unit/qualifier/remove.js000066400000000000000000000013301433026310300221650ustar00rootroot00000000000000require('module-alias/register') require('should') const removeQualifier = require('lib/qualifier/remove') const { guid, hash } = require('tests/unit/utils') describe('qualifier remove', () => { it('should set the action to wbremoveclaims', () => { removeQualifier({ guid, hash }).action.should.equal('wbremovequalifiers') }) it('should return formatted data for one qualifier', () => { removeQualifier({ guid, hash }).data.should.deepEqual({ claim: guid, qualifiers: hash }) }) it('should return formatted data for several qualifiers', () => { removeQualifier({ guid, hash: [ hash, hash ] }).data.should.deepEqual({ claim: guid, qualifiers: `${hash}|${hash}` }) }) }) wikibase-edit-5.3.0/tests/unit/qualifier/set.js000066400000000000000000000071531433026310300214740ustar00rootroot00000000000000require('should') const { instance } = require('config') const { guid, hash, properties } = require('tests/unit/utils') const _setQualifier = require('lib/qualifier/set') const setQualifier = params => _setQualifier(params, properties, instance) describe('qualifier set', () => { it('should rejected if not passed a claim guid', () => { setQualifier.bind(null, {}).should.throw('missing guid') }) it('should rejected if passed an invalid claim guid', () => { const params = { guid: 'some-invalid-guid' } setQualifier.bind(null, params).should.throw('invalid guid') }) it('should rejected if not passed a property', () => { const params = { guid } setQualifier.bind(null, params).should.throw('missing property') }) it('should rejected if not passed a value', () => { const params = { guid, property: 'P2' } setQualifier.bind(null, params).should.throw('missing snak value') }) it('should rejected if passed an invalid value', () => { const params = { guid, property: 'P2', value: 'not-a-valid-value' } setQualifier.bind(null, params).should.throw('invalid entity value') }) it('should rejected if passed an hash', () => { const params = { guid, hash: 'foo', property: 'P2', value: 'Q123' } setQualifier.bind(null, params).should.throw('invalid hash') }) it('should set the hash', () => { const params = { guid, hash, property: 'P2', value: 'Q123' } setQualifier(params).data.snakhash.should.equal(hash) }) it('should set the action to wbsetreference', () => { const params = { guid, property: 'P2', value: 'Q123' } setQualifier(params).action.should.equal('wbsetqualifier') }) it('should format the data for a string', () => { const params = { guid, property: 'P1', value: '123' } setQualifier(params).data.should.deepEqual({ claim: guid, property: 'P1', snaktype: 'value', value: '"123"' }) }) it('should set a time qualifier', () => { const params = { guid, property: 'P4', value: '1802-02' } setQualifier(params).data.should.deepEqual({ claim: guid, property: 'P4', snaktype: 'value', value: '{"time":"+1802-02-00T00:00:00Z","timezone":0,"before":0,"after":0,"precision":10,"calendarmodel":"http://www.wikidata.org/entity/Q1985727"}' }) }) it('should set a time qualifier with precision', () => { const params = { guid, property: 'P4', value: { time: '1802-02', precision: 10 } } setQualifier(params).data.should.deepEqual({ claim: guid, property: 'P4', snaktype: 'value', value: '{"time":"+1802-02-00T00:00:00Z","timezone":0,"before":0,"after":0,"precision":10,"calendarmodel":"http://www.wikidata.org/entity/Q1985727"}' }) }) it('should set a quantity qualifier', () => { const params = { guid, property: 'P8', value: { amount: 123, unit: 'Q4916' } } setQualifier(params).data.should.deepEqual({ claim: guid, property: 'P8', snaktype: 'value', value: `{"amount":"+123","unit":"${instance.replace('https:', 'http:')}/entity/Q4916"}` }) }) it('should set a monolingualtext qualifier', () => { const params = { guid, property: 'P9', value: { text: 'foo', language: 'fr' } } setQualifier(params).data.should.deepEqual({ claim: guid, property: 'P9', snaktype: 'value', value: '{"text":"foo","language":"fr"}' }) }) it('should set a qualifier with a special snaktype', () => { const params = { guid, property: 'P4', value: { snaktype: 'novalue' } } setQualifier(params).data.should.deepEqual({ claim: guid, property: 'P4', snaktype: 'novalue' }) }) }) wikibase-edit-5.3.0/tests/unit/reference/000077500000000000000000000000001433026310300203125ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/unit/reference/remove.js000066400000000000000000000013441433026310300221470ustar00rootroot00000000000000require('module-alias/register') require('should') const removeReference = require('lib/reference/remove') const { guid, hash } = require('tests/unit/utils') describe('reference remove', () => { it('should set the action to wbremovereferences', () => { removeReference({ guid, hash }).action.should.equal('wbremovereferences') }) it('should return formatted data for one reference', () => { removeReference({ guid, hash }).data.should.deepEqual({ statement: guid, references: hash }) }) it('should return formatted data for several references', () => { removeReference({ guid, hash: [ hash, hash ] }).data.should.deepEqual({ statement: guid, references: `${hash}|${hash}` }) }) }) wikibase-edit-5.3.0/tests/unit/reference/set.js000066400000000000000000000051131433026310300214430ustar00rootroot00000000000000require('module-alias/register') require('should') const setReference = require('lib/reference/set') const { guid, properties, hash } = require('tests/unit/utils') describe('reference set', () => { it('should rejected if not passed a claim guid', () => { setReference.bind(null, {}, properties).should.throw('missing guid') }) it('should rejected if passed an invalid claim guid', () => { setReference.bind(null, { guid: 'some-invalid-guid' }, properties) .should.throw('invalid guid') }) it('should rejected if not passed a property', () => { setReference.bind(null, { guid }, properties).should.throw('missing property') }) it('should rejected if not passed a reference value', () => { setReference.bind(null, { guid, property: 'P2' }, properties) .should.throw('missing snak value') }) it('should rejected if passed an invalid reference', () => { const params = { guid, property: 'P2', value: 'not-a-valid-reference' } setReference.bind(null, params, properties).should.throw('invalid entity value') }) it('should set the action to wbsetreference', () => { const params = { guid, property: 'P2', value: 'Q1' } setReference(params, properties).action.should.equal('wbsetreference') }) it('should format the data for a url', () => { const params = { guid, property: 'P7', value: 'http://foo.bar' } setReference(params, properties).data.should.deepEqual({ statement: guid, snaks: '{"P7":[{"property":"P7","snaktype":"value","datavalue":{"type":"string","value":"http://foo.bar"}}]}' }) }) it('should set a reference with a special snaktype', () => { const params = { guid, property: 'P7', value: { snaktype: 'somevalue' } } setReference(params, properties).data.should.deepEqual({ statement: guid, snaks: '{"P7":[{"snaktype":"somevalue","property":"P7"}]}' }) }) it('should accept snaks', () => { const snaks = { P2: 'Q1', P7: [ 'http://foo.bar', { snaktype: 'somevalue' } ] } const params = { guid, snaks } setReference(params, properties).data.should.deepEqual({ statement: guid, snaks: '[{"property":"P2","snaktype":"value","datavalue":{"type":"wikibase-entityid","value":{"entity-type":"item","numeric-id":1}}},{"property":"P7","snaktype":"value","datavalue":{"type":"string","value":"http://foo.bar"}},{"snaktype":"somevalue","property":"P7"}]' }) }) it('should accept a hash', () => { const params = { guid, property: 'P2', value: 'Q1', hash } setReference(params, properties).data.reference.should.equal(hash) }) }) wikibase-edit-5.3.0/tests/unit/request.js000066400000000000000000000013321433026310300204010ustar00rootroot00000000000000require('module-alias/register') require('should') const request = require('lib/request/request') const nock = require('nock') const { shouldNotBeCalled, rethrowShouldNotBeCalledErrors } = require('../integration/utils/utils') describe('request', () => { beforeEach(() => { nock('https://example.org') .get('/') .reply(200, '') }) it('should throw a proper error', async () => { try { await request('get', { url: 'https://example.org', autoRetry: false }).then(shouldNotBeCalled) } catch (err) { rethrowShouldNotBeCalledErrors(err) err.message.should.equal('Could not parse response: ') err.name.should.equal('wrong response format') } }) }) wikibase-edit-5.3.0/tests/unit/sitelink/000077500000000000000000000000001433026310300201765ustar00rootroot00000000000000wikibase-edit-5.3.0/tests/unit/sitelink/set.js000066400000000000000000000040261433026310300213310ustar00rootroot00000000000000require('module-alias/register') const should = require('should') const setSitelink = require('lib/sitelink/set') const { shouldNotBeCalled } = require('root/tests/integration/utils/utils') describe('set sitelink', () => { it('should return wbsetsitelink params', () => { const { action, data } = setSitelink({ id: 'Q123', site: 'frwiki', title: 'Septembre', }) action.should.equal('wbsetsitelink') data.id.should.equal('Q123') data.linksite.should.equal('frwiki') data.linktitle.should.equal('Septembre') should(data.badges).not.be.ok() }) it('should reject without title', () => { try { const res = setSitelink({ id: 'Q123', site: 'frwiki', }) shouldNotBeCalled(res) } catch (err) { err.message.should.containEql('invalid title') err.statusCode.should.equal(400) } }) it('should accept with a null title to delete the sitelink', () => { const { action, data } = setSitelink({ id: 'Q123', site: 'frwiki', title: null, }) action.should.equal('wbsetsitelink') data.id.should.equal('Q123') data.linksite.should.equal('frwiki') should(data.linktitle).be.Undefined() }) it('should accept badges as a string', () => { const { action, data } = setSitelink({ id: 'Q123', site: 'frwiki', title: 'Septembre', badges: 'Q17437796|Q17437798', }) action.should.equal('wbsetsitelink') data.id.should.equal('Q123') data.linksite.should.equal('frwiki') data.linktitle.should.equal('Septembre') data.badges.should.equal('Q17437796|Q17437798') }) it('should accept badges as an array', () => { const { action, data } = setSitelink({ id: 'Q123', site: 'frwiki', title: 'Septembre', badges: 'Q17437796|Q17437798', }) action.should.equal('wbsetsitelink') data.id.should.equal('Q123') data.linksite.should.equal('frwiki') data.linktitle.should.equal('Septembre') data.badges.should.equal('Q17437796|Q17437798') }) }) wikibase-edit-5.3.0/tests/unit/utils.js000066400000000000000000000012631433026310300200540ustar00rootroot00000000000000require('module-alias/register') const helpers = { randomString: () => Math.random().toString(36).slice(2, 10), randomNumber: (length = 5) => Math.trunc(Math.random() * Math.pow(10, length)) } const fixtures = { someEntityId: 'Q1', guid: 'Q1$3A8AA34F-0DEF-4803-AA8E-39D9EFD4DEAF', guid2: 'Q1$3A8AA34F-0DAB-4803-AA8E-39D9EFD4DEAF', hash: '3d22f4dffba1ac6f66f521ea6bea924e46df4129', sandboxStringProp: 'P1' } const properties = { P1: 'String', P2: 'WikibaseItem', P3: 'WikibaseProperty', P4: 'Time', P5: 'ExternalId', P6: 'GlobeCoordinate', P7: 'Url', P8: 'Quantity', P9: 'Monolingualtext' } module.exports = Object.assign({ properties }, helpers, fixtures)